浅谈数据一致性

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

浅谈数据一致性

要解决的问题是什么?

随着业务的规模的不断变大,系统一般都要面临拆分之路:

最开始的单体应用所有功能都在一起,存储也在一起。比如下单->锁定库存->减库存,因为是单体应用,库在一起,创建订单后,直接去更新库存就ok了。

但是拆分后,订单中心、运营系统、库存管理是多个子系统,如何保证数据的一致性?

数据一致性的基础理论

CAP 理论告诉我们,如果不想牺牲一致性,我们只能放弃可用性或分区容错性,这显然不能接受,先简单介绍一下数据一致性的基础理论:

强一致:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。(银行系统一般都是CA或CP模型,CA 模型网络故障时完全不可用,CP 模型具备部分可用性)

弱一致性:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。

最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

最暴力的解决办法 - 业务整合

业务整合方案主要采用将接口整合到本地执行的方法。

拿问题场景来说,则可以将服务 A、B、C 整合为一个服务 D 给业务,比如:

创建一个E服务包含A、B、C

D包含本地服务和E服务,将D服务暴漏出去

优点:解决(规避)了分布式事务。

缺点:显而易见,把本来规划拆分好的业务,又耦合到了一起,业务职责不清晰,不利于维护

由于这个方法存在明显缺点,通常不建议使用

经典方案 - eBay模式

此方案的核心是,通过消息日志的方式来异步执行,消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。

以下单为例:

这是一个经典的解决办法,但是核心问题是,如何保证服务接口的幂等性?如何避免重复发送消息带来的问题?

所以基于以上办法,分为两个阶段处理:

第一阶段:通过本地的数据库的事务保障,增加了 transaction 表或消息队列

第二阶段:分别读出消息队列(但不删除),通过判断更新记录表 updates msg 来检测相关记录是否被执行,未被执行的记录会修改库存表,然后增加一条操作记录到 updates msg

优先使用消息进行处理

如果业务逻辑无法保证幂等,则要增加一个去重表或者类似的实现。对于 producer 端在业务数据库的同实例上放一个消息库,发消息和业务操作在同一个本地事务里。发消息的时候消息并不立即发出,而是向消息库插入一条消息记录,然后在事务提交的时候再异步将消息发出,发送消息如果成功则将消息库里的消息删除,如果遇到消息队列服务异常或网络问题,消息没有成功发出那么消息就留在这里了,会有另外一个服务不断地将这些消息扫出重新发送。

如何判断接收方是否成功执行?

可能会有两个问题

1、消息通知往往不能保证 100% 成功

2、消息通知后,接收方业务是否能执行成功还是未知数

问题1可以通过重试解决;问题2可以通过事务消息来保证,可以选择基于 DB 事件变化通知到 MQ 的方式做系统间解耦,通过订阅方消费 MQ 消息时的 ACK 机制,保证消息一定消费成功,达到最终一致性。

如何应对实时同步做、强一致性要求的业务场景?

引入类似二段提交的事务框架,会带来复杂性的急剧上升,可以采用准实时的最终一致性

以下单->锁定库存-> 扣除库存为例:

1、首先创建一个不可见的订单

2、同步调用锁定库存,锁定库存成功,扣除库存,失败,发出消息,废弃订单

3、扣除库存成功,订单变为可见,扣除库存失败,发出消息,废弃订单,并判断是否回滚库存

通过HTTP解决幂等性

int create_ticket()

boolean updateAmpunt(ticket id, account id, amount)

HTTP GET方法用于获取资源,不应有副作用,所以是幂等的

HTTP DELETE方法用于删除资源,有副作用,应该满足幂等性

POST和PUT可以简单的理解为,POST表示创建资源,PUT表示更新资源,但是它们更大的区别在与幂等性上

POST所对应的URI并非创建的资源本身,而是资源的接收者,所以,POST方法不具备幂等性;例如 POST /news 创建一个新闻,两次请求创建两次,它们具有不同的URI

PUT /news/1 用于更新或创建ID为1的新闻,应该具有幂等性

实例:

PUT:/accounts/account_id&amount=xxx

↓ ↓ ↓

POST:/tickets

PUT:/accounts/account id/ticket id&amount=xxx

实际使用,amount应该放在body体中

还有哪些应用场景?

总结

1、分布式的不可控性到目前为止其实都没有一个非常完善的机制来解决,而是尽量的使用各种策略方法来规避各种问题,以达到事务的一致性。

2、将大的流程拆分为多个小的本地事务,对于非实时、非强一致性的业务,在本地事务执行成功后,我们选择发消息通知,然后依靠重试等方式达到最终一致性

3、消息日志方案的核心是保证服务接口的幂等性

4、方案总结下来就是:拆分-重试-记录,我们把它称为“记帐”模式。把整个的事务切分成多个单个事务,最后由job把这些事务的对象发送到一组消息机器。这时处理方式分成两部,前台直接给用户返回提交成功;服务器开始自己的工作。服务器端会有一组机器不停的去取消息中的数据,取到数据后就执行,并且每次都记录下取到的数据和版本号(CAS)等等信息,这些信息用来作为判重的依据,这样依次的执行队列中的消息,当执行过程中发现异常的时候,还是自动的去回滚消息,如果发生硬件级别的问题,也是通过通知机制来强行的恢复数据。

其他

分布式ID算法?

事务补偿?

如何和RCP框架结合,比如dubbo?

相关文档
最新文档