在分布式数据库中,牺牲部分一致性换取可用性,可显著提升系统的可扩展性

本文探讨了在分布式数据库中,为了实现系统的可扩展性,可以牺牲部分一致性来提高可用性。通过对ACID事务模型的分析,提出了BASE原则,包括基本可用、软状态和最终一致性,作为替代方案。通过示例展示了如何在系统设计中应用BASE原则,以减少对一致性要求,同时保证系统的高可用性。
摘要由CSDN通过智能技术生成

In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability.

     Web应用程序在过去十年快速发展、越来越普遍。作为应用开发人员,当然希望你的系统会被很多人使用。随之而来的是事务的增长。如果你的系统依赖于持久化,数据存储很可能会是你的系统瓶颈。

       任何应用程序都有两种扩展的策略。第一种是垂直扩展,也是最简单的一种:将应用迁移动更强大的计算机。对数据来说垂直扩展有效,但存在一些限制。最明显的限制就是大型系统的数据容量的快速增长。垂直扩展费用高昂,通常需要购买下一代更新的系统来满足额外的需求。垂直扩展通常会依赖于特定供应商,进一步增加成本。

      水平扩展提供更大的灵活性,也会更加复杂。数据的水平扩展可从维度进行。Functional Scaling 包含按功能对数据进行分组,并按功能将数据分布在不同数据库中。第二个维度是对同一功能组内的数据进行数据分片。数据的分片如图1所示。

 

 

CAP理论

加州伯克利大学教授、Inktonmi共同创始人及首席科学家Eric Brewer认为web服务不能同时满足以三个属性:

1.         一致性。客户端可立刻感知一组操作带来的变化。

2.         可用性。每次的操作必须以既定的响应结束。

3.         分区容错性:即使部分组件不可用,操作仍然可完成。

Web程序只能支持最多两个属性,不论采用哪种数据库设计方案。水平扩展显然是基于数据分区。因此设计人员只能从一致性和可用性中做出选择。

 

ACID解决方案

ACID数据库的事务极大简化应用开发人员的开发难度。ACID提供以下保证:

1.         原子性:在事务中的所有操作要么都执行,要么都不执行。

2.         一致性:数据库在事务执行前后会处于一致状态。

3.         隔离性:事务之间不会互相影响,如同只有一个事务在执行。

4.         持久性:事务完成之后,操作会永久保存在数据库中。

数据库厂商很久以前就认识到有需要数据切片的需求,并引入两阶段提交(2PC)的技术用于跨多数据库实例的ACID的保证。该协议分成两个阶段:

l  首先,事务协调者询问相关的数据库是否可以进行precommit操作,以表明是否可以进行commit。如果所有的数据库都同意可以进行commit,则进行第二阶段。

l  事务协调者请求所有数据库提交数据。

      如果有任一数据库不同意commit,所有的数据库会被要求回滚事务中属于自己的那一部分。这有什么缺点?通过这种方式实现了分区的一致性。如果Brewer的观点是对的,那么可用性一定受到影响。但是这是怎么一回事呢?

      任何系统的可用性是由操作涉及的组件的可用性的乘积。并不要求构成系统的组件不能对系统的可用性带来影响。在2PC提交中,一个事务如果包含两个数据库,那么可用性就等于两个数据库可用性的乘积。举个粟子,我们假定每个数据库的可用性是99.9%,那么事务的可用性就是99.8%,也就意味着每个月系统的故障时间增加了43分钟。

 

ACID外的另一选择

     如果ACID提供了分布式数据库的一致性,那么你应该怎么样实现可用性?有一种方法是BASE(基本可用、软状态、最终一致性)。

     BASE正好与ACID相反。ACID是一种悲观策略,强制要求每次操作结束后处于一致状态。BASE是乐观策略,允许数据库的一致性是变化的。这听起来似乎没有办法处理,但实际上是可控的并提高系统的可扩展水平,这是ACID所没有办法做到的。

     BASE的可用性是通过允许系统的部分失效来实现,即分区容错性。举个粟子,将数据分布到5个数据库服务器上,基于BASE的设计推荐通过特定的操作,只让20%的用户会因为特定主机的失效受到影响。这没有什么神奇之处,却能带来更高的系统可用性。

     既然你已经到你的数据分解成不同的功能组,并将最热的功能组分散到多个数据库中,怎么将BASE植入到你的系统中?BASE需要对一个逻辑事务中的操作进行深入分析,这和ACID不太一样。需要关注的是什么?接下来的部分会提供一些指导。

 

一致性模型

      BASE保证分区数据库的可用性,那么需要识别出可放松一致性要求的地方。这通常很难,因为老板和开发人员都认为一致性是一个系统能获取成功的重要保证。临时的不一致性必然会被用户所感知,因此需要开发人员、产品使用人员共同寻找可放松一致性要求的地方。

图2解释了BASE对如何考虑一致性问题的。User表包含用户的相关信息,如总销售额、总购买金额。Transaction表包含与每一个事务相关的seller、buyer和数额信息。这些信息对实际的表进行了简化,但包含了必要的信息,足以说明一致性问题。

     通常来说,与一个功能组内的一致性进行放松则相比,跨多个功能组的一致性进行放松容易一些。在示例中有两个功能组:users和transactions。每当销售一件商品,一行记录就会添加到transcation表中,买家和卖家的帐薄也会进行更新。使用ACID风格的事务,SQL如图3所示。

User表中的购买列和销售列可以认为是transaction表的一个副本,只是出于效率的考虑出现在此处。基于以上原因,一致性约束要求可以进行放松。也就是对buyer和seller的操作可以设计成不会立即反映事务的执行结果。在实际生活中,这种情况并不少见。如AMT取款、电话账单。

         对SQL语句进行修改,以对一致性进行放松需要依赖于累计余额是怎么定义的。如果只是一个预估值,就意味着一些事务丢失可以接受,要做的改变就很简单,如图4所示。

      我们现在已经将用户表、事务表的更新进行了解耦。表之间的一致性已不保证。事实上,在事务一和事务二之间发生故障会导致用户表永久处于不一致状态,如果合同规定累计余额是一个预估值的话那么也就足够了。

       如果预估值是不被授受的话,又该怎么办?是否仍然有办法将用户、事务表的更新进行解耦?引入持久化消息队列可以解决这个问题。有多种方法实现持久化消息。实现队列最关键的因素是保证后备持久化是在相同的资源上实现的。这对于队列不使用2PC的事务提交是非常重要的。现在SQ斩操作看起来有些区别,如图5所示。

       将持久化消息以一个事务的方式放入队列中,用于更新累计余额的信息就可以获取。事务存储在单数据库实例中,因此不会影响系统的可用性。

         一个独立的事务处理组件会对每一条消息进行出队处理,将信息更新到user表中。这个例子似乎解决了所有的问题,但实际并非如此。消息持久化是用于事务主机在消息入队时避免2PC。如果消息在事务中出队操作包含了用户主机,那么我们仍然是处于2PC的情况。

         有一种解决方案是在消息处理的2PC中什么事情也不做。将更新操作解耦,放到一个单独的后端组件,从而实现与用户直接相关的组件的可用性。也许你的业务需要求可以接受不是那么可靠的的消息处理。

         假设你的系统永远不能接受2PC。那么这个问题怎么解决?首先,你需要理解幂等性的概念。一个操作如果可以执行一次或多次的结果都是相同的,那么这种操作是幂等的。幂等操作非常有用,因为这样的操作允许部分失效,因此重复执行这些操作不会改变系统的最终状态。

         我们前面所选的例子是不满足幂等性的。更新操作很少是幂等的。上述例子在余额列累加余额就是非幂等的。多次执行这些操作很显然会导致不正确的结果。如果考虑操作的顺序,即使更新操作只是设置了一个值,也是非幂等的。如果系统无法保证更新操作的顺序性,那么最终结果会是不不正确的。

         在余额更新的这个例子中,你需要跟踪哪些更新操作已经成功执行,哪些还未执行。一种方法是使用一张表来记录已经执行的事务的ID。

         如图6所示,该表记录了事务的ID、更新后的余额。我们的伪代码如图7所示。

         通过查看队列中的消息,并在成功处理之后,将其移除。如果有需要,可以在两个独立事务中执行:一个在消息队列,一个在用户数据库。队列上的操作并不会提交,直到数据库操作成功提交之后才会提交。该算法支持部分失效、且在不使用2PC的情况下仍然提供事务保证。

         如果需要考虑顺序性,这是一个简单的方法来保证更新的幂等。对样例schema进行一些改动,如图8所示。

      假设你记录了用户的最近的销售日期和购买日期。也可以使用类似的schema来更新带有消息的日期,但存在一个问题。

      假设两笔交易在一个比较短的时间窗口,消息系统没有办法顺序操作。在这种情况下,由于依赖于消息的处理顺序,没有办法保证last_purchase值的正确性。好在只要对这种更新操作的SQL进行一些小小的改动就可以得到解决,如图9所示。

    只要不允许last_purchase时间的值于的更新到更旧的时间值,使得对订单的更新操作是独立的。也可以使用该方法来避免乱序的消息对更新的影响。除了使用时间外,单调递增的事务id也是一种选择。

 

消息队列的顺序性

        消息系统可以保证消息的顺序到达。但带来的开销是比较大的,通常也没有必要。事实上,有时会产生一个系统是安全的错觉。

        这里提供的例子解释了消息的顺序性要求可以放松,并提供了数据库视图的最终一致性。放松顺序性的开销是比较小的,并且在多数情况下是远小于保证消息的有序性所付出的开销。

        此外,不论采用哪种交互试,web应用程序在语义上是一个事件驱动系统。客户端请求是无序到达的。每个请求的处理时间也是不同的。请求在整个系统中的组件进行调度也是不确定的,最后也会使消息的顺序也是不确定的。要求保证顺序性会产生安全性的错觉。实际上,不确定的输入会产生不确定的输出。

软状态、最终一致性

        到目前为止,重点关注的都是丢弃部分一致性用于提高可用性。另外一方面,我们需要理解系统设计中软状态和最终一致性所带来的影响。

        作为开发人员我们更喜欢把系统视为一个闭合循环。我们理解的行为可预测性是指可预测的输入产生可预测的输出。这对于开发一个正确的软件是非常重要的。好消息是,在许多情况下使用BASE不会改变系统的可预测性,但是从整体上观察系统的行为。

        举个简单的例子可以帮助我们更好地理解。就来一个用户可以进行资产转移的系统来说,资产的类型是无关的,可以是钱也可以是游戏中的物品。我们假定我们将从一个用户移除一项资产的操作和将该资产给另外一个用户的操作进行解耦,具体实现是通过使用消息队列来实现。

        系统会马上处于不确定状态。尽管时间窗的大小可由消息系统的设计来确定,该资产会在一个时间段内处于不属性任何一个用户的状态。

        从用户的角度看,用户可能不会感知到这段时间差。不论是发送用户,还是接收用户都不知道资产是否已经到达。如果时间差只是几秒钟,用户就不会感知到或者是可以接受的。在这种情况下,则认为系统的行为是处于一致状态,并且用户是可授受的。,虽然我们的实现是依赖于软状态和最终一致性。

事件驱动体系结构

        如果你需要知道状态什么时候变成了一致,那需要做什么?当状态到达一致性时,你可能相关的算法来处理。一种简单的方法是依赖于状态变为一致时产生的事件。

        继续以前面的例子为例,如果你需要通过用户资源已经到达,应该做什么呢?在提交资产给接收用户的事务中创建一个事件,提供了后续到达某种状态时进一步处理的机制。EDA带来了极大的可扩展水平提升和系统的解耦。关于事件驱动系统的问题不在本文的讨论范围。

总结

    对事务相关系统的扩展需要一种全新的管理资源方法。传统的事务模型在涉及到许多组件时存在一些问题。将多个操作进行解耦,并依次执行这些操作可提高系统的可靠性,并降低保持一致性的开销。BASE提供了一种可用于解耦的模型。

参考文献

  1. http://highscalability.com/unorthodox-approachdatabase-design-coming-shard.

  1. http://citeseer.ist.psu.edu/544596.html

作者

    Dan Partchett是eBay的一名技术员,加入系统架构组已经4年。超过20年在技术公司工作的经验,如Sun, HP, Silicon Graphics, Partchett有丰富的技术经验,涉及网络协议、操作系统、系统设计、软件模式。从密苏里大学罗拉分校获得计算机科学学士学位。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值