在建立优步大型支付系统过程中学到的分布式架构概念

这是优步Uber员工Gergely Orosz自己的经验分享:

两年前我加入了优步,担任移动软件工程师,有一些后端经验,我构建了带有支付功能的应用程序 - 并在后来重写了它。之后,我就晋升工程管理并领导了一个团队。这意味着需要接触更多的后端,因为我的团队负责支持支付的许多后端系统。

在优步工作之前,我几乎没有分布式系统经验。我的背景是传统的计算机科学学位和十年的全面软件开发。然而,虽然我能够画图并谈论权衡,但我对分布式概念(如一致性,可用性或幂等性)没有太多的了解或理解。

在这篇文章中,我总结一些在构建大规模,高可用性的分布式系统时应该学习的基本概念。我们的优步支付系统是一个负载高达每秒数千次请求的系统,即使部分系统停机,关键支付功能也需要正常工作。这里讨论的基本概念是一个完整的清单吗?可能不会。但是,如果我早点知道他们的话,那会让我的生活变得更轻松。因此,让我们深入了解SLA、一致性、数据持久性、消息持久性、幂等性以及我在工作中需要学习的其他一些内容。

SLA
对于每天处理数百万事件的大型系统,有些事情肯定会出错。在深入规划系统之前,我发现决定“健康”系统最重要的因素是什么。“健康”应该是实际可衡量的。衡量“健康”的常见方式是SLA:服务级别协议。我见过的一些最常见的SLA是:

可用性:服务运行时间的百分比。虽然拥有100%可用性的系统很诱人,但实现起来可能非常困难,而且很昂贵。即使是像VISA卡网络,Gmail或互联网提供商这样的大型和关键系统也没有100%的可用性 - 多年来,它们也会宕机数秒,数分钟或数小时。对于许多系统而言,四个九个可用性(99.99%,或每年约50分钟的停机时间)被视为高可用性。刚刚达到这个水平通常需要相当辛苦的工作付出。

准确性:系统中的某些数据不准确或丢失可以吗?如果是这样,可以接受多少百分比?对于我所从事的支付系统,准确度需要达到100%,这意味着不允许丢失任何数据。

容量:系统能够支持的预期负载是多少?这通常以每秒请求数来表示。

延迟:系统在多长时间内响应?95%的请求在多长时间到达,99%的请求在多长时间内到达?系统通常有很多嘈杂的请求,因此.p95和p99延迟在现实世界中更实用。

为什么建立大型支付系统时SLA很重要?我们需要整合了一个新系统,取代了现有旧系统。为了确保我们构建的是正确的事情,是一个“更好”的系统,我们使用SLA来定义期望值。可用性是我们的最高要求之一。一旦确定了目标,我们就需要考虑通过架构的折衷才能达到这个目的。

水平与垂直缩放
假设新建系统的使用业务量增长,负载就会增加。在某些情况下,现有设置将无法支持更多负载并需要添加更多容量。两种最常见的缩放策略是垂直或水平缩放扩展。

水平缩放扩展是增加更多的机器(或节点)到系统,以增加容量。水平缩放是扩展分布式系统最流行的方式,特别是,向集群添加(虚拟)机器通常与单击按钮一样容易。

垂直缩放基本上是“购买更大/更强的机器” - 或者是具有更多内核的(虚拟)机器、更多处理器、更多内存。对于分布式系统,垂直缩放通常不太流行,因为它可能比水平缩放更昂贵。但是,一些主要站点,如Stack Overflow已经成功地通过垂直扩展满足了需求。

为什么在构建大型支付系统时扩展策略很重要?我们很早就决定建立一个横向水平扩展的系统。尽管在某些情况下可以实现垂直扩展,单个超级昂贵的大型机在今天也可以满足需要。 我们团队的工程师也曾在大型支付服务提供商那里工作过,当时他们试图购买的最大机器上进行垂直纵向扩展,但是未能成功。

一致性
任何系统的可用性都很重要。分布式系统通常建立在具有较低可用性的机器之上。假设我们的目标是构建一个具有99.999%可用性(每年约宕机5分钟)的系统。我们使用的机器/节点平均具有99.9%的可用性(它们每年约宕机8小时)。一个直接获得我们的可用性数值的方法是将一堆机器/节点添加到群集中。即使某些节点停机,其他节点也会启动,系统的整体可用性将高于单个组件的可用性。

一致性是高度可用系统中的关键问题。如果所有节点同时查看并返回相同的数据,则系统是一致的。回到之前的模型,我们添加了一堆节点以实现更高的可用性,确保系统保持一致不是微不足道的。为了确保每个节点具有相同的信息,他们需要互相发送消息,以保持自己的同步。但是,发送给对方的消息可能无法传递,它们可能会丢失,并且某些节点可能不可用。

一致性是我花最多时间理解和欣赏的概念。有几种一致性模型,分布式系统中最常用的模型是强一致性、弱一致性和最终一致性。关于最终vs强一致性,Hackernoon的文章给出了在这些模型之间的进行折衷选择的实用概述。通常,所需的一致性越弱,系统的速度越快,但返回的就可能不是最新的数据集。

为什么建立大型支付系统时一致性很重要?系统中的数据需要保持一致。但是如何保持一致?对于系统的某些部分,只有强一致的数据才能做到。例如,了解付款是否已经开始需要以强一致的方式进行存储。对于其他部分而言,如果不是关键任务,最终的一致性可以被认为是合理的权衡。一个很好的例子就是列出最近的交易:这些交易可以通过最终的一致性来实现(也就是说,最近的交易可能会在一段时间后才会显示在系统的一部分中),作为回报,操作将以较低的延迟返回,或者资源密集度较低)。

数据持久性
持久性意味着一旦数据成功添加到数据存储中,它就可以继续使用。即使系统中的节点脱机,崩溃或数据损坏,情况也会如此。

不同的分布式数据库具有不同级别的持久性 有些数据库支持机器/节点级别的持久性,有些支持集群级别的,有些则不提供开箱即用。通常使用某种形式的复制来提高持久性 - 如果数据存储在多个节点上,并且一个或多个节点脱机,但是数据仍然是可用。这里有一篇很好的文章,说明为什么在分布式系统中实现耐久性可能会有挑战.

为什么建立支付系统时数据的持久性很重要?因为不会丢失任何数据这是很重要的,比如付款就不能丢失付款数据。我们构建的分布式数据存储需要支持集群级数据持久性 - 所以即使个别实例会崩溃,已完成的交易也会持续存在。现在,大多数分布式数据存储服务(如Cassandra,MongoDB,HDFS或Dynamodb)都支持各种级别的持久性,并且都可以配置为提供群集级别的持久性。

消息持久性和耐久性
分布式系统中的节点执行计算,存储数据并将消息发送给对方。发送消息的关键特征是这些消息到达的可靠程度。对于任务关键型系统,通常只能丢失零个消息。

对于分布式系统,消息传递通常由一些分布式消息服务完成,例如RabbitMQ,Kafka或其他。这些消息传递服务可以支持(或被配置为支持)传递消息的不同级别的可靠性。

消息持久性意味着当处理消息的节点发生某些故障时,消息仍然会在故障解决后进行处理。消息持久性通常用于消息队列级别。使用持久的消息队列时,如果队列(或节点)在发送消息时脱机,则消息恢复联机时仍会收到消息。阅读关于这个主题的更好的文章就是这个


为什么建立大型支付系统时信息的持久性和耐用性很重要?我们不能承受消息的丢失,比如一个人已经开始支付乘坐费用的消息就不能丢失。这意味着我们使用的消息传递系统必须是无损的:每条消息都需要传递一次。但是,构建一个系统可以将每条消息准确传递一次,或者至少传送一次消息的系统的复杂程度不同。我们决定实现一个持久的消息传递系统,至少一次传递,并选择了一个消息总线,我们将在此之上构建这个消息总线(我们最终使用Kafka设计了一个无损集群系统)。

幂等
对于分布式系统,任何事情都可能会出错,例如连接可能中途中断或请求超时。客户经常会重试这些请求。幂等系统确保无论执行特定请求多少次,对此请求的实际执行只发生一次。一个很好的例子就是付款。如果客户发出支付请求,请求成功,但客户端超时,客户端可能会重试同样的请求。用幂等系统,支付的人不会被收取两次费用。

为幂等而设计的分布式系统需要某种分布式锁策略。这是早期分布式系统概念的一部分。假设我们打算通过实施乐观锁定来实现幂等性,以避免并发更新。为了进行乐观锁定,系统需要保持强一致性 - 以便在操作时,我们可以使用某种版本控制来检查是否已启动另一个操作。

取决于系统的限制和操作类型,有许多方法可以实现幂等性。设计幂等方法是一个很好的挑战 - Ben Nadel写了他使用的不同策略,包括分布式锁或数据库约束。在设计分布式系统时,幂等性可能是容易被忽视的部分之一。我遇到过各种情况,我的团队因为没有确保某些关键操作的正确幂能力而被烧毁。

为什么建立大型支付系统时幂等性很重要?最重要的是:避免双倍收费或双倍退款。鉴于我们的消息传递系统至少有一次无损传递,我们需要假设所有消息可能会多次传递,系统需要确保幂等性。我们选择使用版本控制和乐观锁定来处理这个问题,让实现幂等行为的系统使用强一致的存储作为其数据源。

分片和法定人数
分布式系统通常必须存储更多数据,单个节点通常做不到这点。那么,如何在一定数量的机器上存储大量数据呢?最常见的技术是使用分片。数据使用某种散列进行水平分区以分配给分区。尽管许多分布式数据库在底层实现分片,但分片是一个有趣的领域,可以更多地进行了解,特别是重新分片。由于Foursquare在2010年发生了一个分裂的边缘案例,因此Foursquare在2010年有17个小时的停机时间,在这个案例中,这篇文章分享了一个很好的事后分析。

许多分布式系统在多个节点上复制数据或计算。为了确保操作以一致的方式执行,定义了基于投票的方法,其中一定数量的节点需要获得相同的结果,最后才能使得整个操作成功。这被称为法定人数Quorum。

在优步建立支付系统时,为什么法定人数和分片是重要的?这些都是很常用的基本概念。当我看看我们如何设置Cassandra复制时,我个人遇到了这个概念。Cassandra(和其他分布式系统)使用法定人数和本地法定人数来确保跨集群的一致性。作为一个有趣的副作用,在我们的一些会议上,当有足够多的人在会议室时,有人会问:“我们可以开始吗?我们有法定人数吗?”

演员Actor模型
描述编程实践的常用术语是 变量、接口、调用方法等 - 这些假定前提都为单机系统。在谈到分布式系统时,我们需要使用一套不同的方法。描述这些系统的常见方式是遵循Actor模型,我们在通信方面考虑代码。这种模式很受欢迎,因为它与我们想到的心智模式相匹配,例如,在组织中的人们是如何进行沟通的场景类似。另一种描述分布式系统的流行方式是CSP--传递顺序过程。

演员模型基于演员互相发送消息并对其作出反应。每个角色都可以做一些有限的事情 比如- 创建其他角色,向他人发送消息或决​​定如何处理下一条消息。通过一些简单的规则,可以很好地实现一个复杂的分布式系统,这些系统也可以在演员崩溃后自行修复。我推荐Brian Storti 文章在10分钟内了解演员模型。许多语言都实现了actor库或框架。例如,在Uber,我们使用Akka工具包。

为什么在建立大型支付系统时,这个演员模型很重要?我们正在建立一个拥有许多工程师的系统,其中很多人都拥有分布式的经验。我们决定遵循标准的分布式模型,自己提出分布式概念,可能是重新发明轮子。

响应式架构
在构建大型分布式系统时,其目标通常是使其具有韧性、弹性和可扩展性。对于一个支付系统或一个高负载系统,都需要类似这样模式。业内人士一直在发现并分享在这些情况下运行良好的最佳实践 - 而响应式/反应式架构是这一领域中流行和广泛使用的模式。

要开始使用Reactive Architecture,我建议阅读Reactive Manifesto并观看关于该主题的12分钟视频

为什么建立大型支付系统时Reactive Architecture很重要? 我们使用工具包 Akka来构建新的支付系统,这个工具包就受到了响应式架构的影响。我们许多工程师构建新系统时也同时熟悉了响应式编程的最佳实践。遵循响应原则 - 建立一个响应灵活、弹性好、有韧性和消息驱动系统变得非常自然。

总结
我很幸运能够参与重建一个高规模,分布式的关键任务系统:优步的支付系统。通过在这种环境中工作,我选择了许多我以前不必使用的分布式概念。我总结了这些内容,希望对其他人开始或继续学习分布式系统有所帮助。

Distributed architecture concepts I learned while