保障分布式系统数据一致性
2016-05-15 10:31
232 查看
对于现在普遍的分布式系统的架构和设计, 在提升系统可用性的同时, 各个子系统之间的数据一致性问题也日益凸显出来, 很多公司都推出了符合自己业务需求的解决方案
具体业务场景如下,比如一个业务操作,如果同时调用服务 A、B、C,需要满足要么同时成功;要么同时失败。A、B、C 可能是多个不同部门开发、部署在不同服务器上的远程服务。
在分布式系统来说,如果不想牺牲一致性,CAP 理论告诉我们只能放弃可用性,这显然不能接受.
在工程实践上,为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性
BASE (basically available, soft state,
eventually consistent)
BASE
的可用性是通过支持局部故障而不是系统全局故障来实现的, 如果将用户分区在
5 个数据库服务器上,BASE 设计鼓励类似的处理方式,一个用户数据库的故障只影响这台特定主机那 20% 的用户。这里不涉及任何魔法,不过它确实可以带来更高的可感知的系统可用性
场景如下,
产生一笔交易, 第一步,需要在交易表增加记录. 第二部需要修改用户金额表, 买家金额减少, 卖家金额增加. 而且卖家与买家可能分布在不同的数据库服务器
u
对于每一个分库, 增加一个更新记录表 updates_applied 来记录已经处理过的消息(处理完一笔,
记录这样一条消息, 放在同一个事务里面), 这样做是为了保障处理的幂等性.
伪代码如下:
第一阶段:
通过本地的数据库的事务保障,增加了 transaction 表及消息队列, 消息队列中记录了本次事务分别要做的事情的步骤
第二阶段:
分别读出消息队列(但不删除),通过判断更新记录表 updates_applied 来检测相关记录是否被执行
针对不同的用户记录交易记录, 假设根据用户id分库分表, 分别开启事务未被执行的记录会修改
user 表,
然后增加一条操作记 录到 updates_applied,这两个步骤要保证在同一个数据库实例的事务中来完成.
事务执行成功之后再删除队列中对应的消息.
这样做的好处是: 对事务中的每一个步骤都记录到消息中, 相当于对做的事情记录了日志. 可以对日志记录进行不断的重试操作. 但是未考虑到回滚的动作.
比如转账失败, 或者买家的余额不足这种情况下, 卖家方的操作如何回滚.
交易创建的一般性流程
面临的问题:
每个功能点的实现都可能会依赖外部服务。那么如何保证各个服务之间的数据是一致的呢?比
如锁定优惠券服务调用超时了,不能确定到底有没有锁券成功,该如何处理?再比如锁券成功
了,但是扣减库存失败了,该如何处理?
方案选型
1. 服务依赖过多, 会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10个服务,
9个都执行成功了,最后一个执行失败了,那么是不是前面9个都要回滚掉? 这个成本还是非常高的。
所以在拆分大的流程为多个小的本地事务的前提下,对于非实时、非强一致性的关联业务写入,
在本地事务执行成功后,我们选择发消息通知、关联事务异步化执行的方案.
2. 消息通知往往不能保证100%成功; 且消息通知后,接收方业务是否能执行成功还是未知数
第一个问题: 通过重试来解决, 保证消息一定要到达broker
第二个问题: 订阅方消费消息的ack机制来保障消息一定消费成功. 消息可能被重发,
所以业务方在处理消息的时候需要做到幂等性.
3. 对于需要实时同步做, 有强一致性需求的业务场景, 在交易创建过程中, 锁券和扣减库存是这样的两个典型场景
乍一看,必须要 引入分布式事务框架才能解决。但引入非常重的类似二阶段提交分布式事务框架会带来 复杂性的急剧上升;在电商领域,绝对的强一致是过于理想化的,我们可以选择准实时的最终一致性
a. 我们在交易创建流程中,首先创建一个不可见订单,然后在同步调用锁券和扣减库存时,针对调用异常(失败或者超时),发出废单消息到MQ
b. 如果消息发 送失败,本地会做时间阶梯式的异步重试
c. 优惠券系统和库存系统收到消息后,会进行判断是否需要做业务回滚,这样就准实时地保证了多个本地事务的最终一致性
具体业务场景如下,比如一个业务操作,如果同时调用服务 A、B、C,需要满足要么同时成功;要么同时失败。A、B、C 可能是多个不同部门开发、部署在不同服务器上的远程服务。
在分布式系统来说,如果不想牺牲一致性,CAP 理论告诉我们只能放弃可用性,这显然不能接受.
在工程实践上,为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性
1.规避分布式事务——业务整合
一般来说不可取2. ebay模式
将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理BASE (basically available, soft state,
eventually consistent)
BASE
的可用性是通过支持局部故障而不是系统全局故障来实现的, 如果将用户分区在
5 个数据库服务器上,BASE 设计鼓励类似的处理方式,一个用户数据库的故障只影响这台特定主机那 20% 的用户。这里不涉及任何魔法,不过它确实可以带来更高的可感知的系统可用性
场景如下,
产生一笔交易, 第一步,需要在交易表增加记录. 第二部需要修改用户金额表, 买家金额减少, 卖家金额增加. 而且卖家与买家可能分布在不同的数据库服务器
u
对于每一个分库, 增加一个更新记录表 updates_applied 来记录已经处理过的消息(处理完一笔,
记录这样一条消息, 放在同一个事务里面), 这样做是为了保障处理的幂等性.
伪代码如下:
第一阶段:
通过本地的数据库的事务保障,增加了 transaction 表及消息队列, 消息队列中记录了本次事务分别要做的事情的步骤
第二阶段:
分别读出消息队列(但不删除),通过判断更新记录表 updates_applied 来检测相关记录是否被执行
针对不同的用户记录交易记录, 假设根据用户id分库分表, 分别开启事务未被执行的记录会修改
user 表,
然后增加一条操作记 录到 updates_applied,这两个步骤要保证在同一个数据库实例的事务中来完成.
事务执行成功之后再删除队列中对应的消息.
这样做的好处是: 对事务中的每一个步骤都记录到消息中, 相当于对做的事情记录了日志. 可以对日志记录进行不断的重试操作. 但是未考虑到回滚的动作.
比如转账失败, 或者买家的余额不足这种情况下, 卖家方的操作如何回滚.
3. 蘑菇街交易创建过程中的分布式一致性方案
交易创建的一般性流程面临的问题:
每个功能点的实现都可能会依赖外部服务。那么如何保证各个服务之间的数据是一致的呢?比
如锁定优惠券服务调用超时了,不能确定到底有没有锁券成功,该如何处理?再比如锁券成功
了,但是扣减库存失败了,该如何处理?
方案选型
1. 服务依赖过多, 会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10个服务,
9个都执行成功了,最后一个执行失败了,那么是不是前面9个都要回滚掉? 这个成本还是非常高的。
所以在拆分大的流程为多个小的本地事务的前提下,对于非实时、非强一致性的关联业务写入,
在本地事务执行成功后,我们选择发消息通知、关联事务异步化执行的方案.
2. 消息通知往往不能保证100%成功; 且消息通知后,接收方业务是否能执行成功还是未知数
第一个问题: 通过重试来解决, 保证消息一定要到达broker
第二个问题: 订阅方消费消息的ack机制来保障消息一定消费成功. 消息可能被重发,
所以业务方在处理消息的时候需要做到幂等性.
3. 对于需要实时同步做, 有强一致性需求的业务场景, 在交易创建过程中, 锁券和扣减库存是这样的两个典型场景
乍一看,必须要 引入分布式事务框架才能解决。但引入非常重的类似二阶段提交分布式事务框架会带来 复杂性的急剧上升;在电商领域,绝对的强一致是过于理想化的,我们可以选择准实时的最终一致性
a. 我们在交易创建流程中,首先创建一个不可见订单,然后在同步调用锁券和扣减库存时,针对调用异常(失败或者超时),发出废单消息到MQ
b. 如果消息发 送失败,本地会做时间阶梯式的异步重试
c. 优惠券系统和库存系统收到消息后,会进行判断是否需要做业务回滚,这样就准实时地保证了多个本地事务的最终一致性
相关文章推荐
- android圆角按钮自定义
- opencv寻找轮廓2--drawContours
- L1-3. 个位数统计-Java
- java 过滤器(Filter)学习笔记
- Javascript基础知识盲点总结之函数
- android开发笔记之多媒体—SurfaceView
- 初始 java 反射机制 (一)
- 第八周项目训练3
- MySql之空值与非空与自动编号以及约束
- 深入理解$.each和$(selector).each
- servlet代码分析-整个执行流程
- 【LeetCode】54. Spiral Matrix 解题报告
- 第十一周学习进度条
- part1:3-VMware及redhat enterprise Linux 6 的安装
- think php 快捷函数
- 齐次坐标的理解
- 图表的基本配色
- android:descendantFocusability
- ACM中JAVA的使用
- 机器学习基础