问题描述
各个子系统数据一致性问题
在过往单机系统的时代,把相关操作放在一个事务里,就能为我们解决数据一致性的问题。但在分布式系统和微服务架构盛行的今天,常常会遇到一个操作需要依赖多个外部服务的场景。要求我们自行解决各个系统数据一致性的问题。
解决方案
补偿事务
互联网场景下,解决分布式系统的一致性问题,基于系统复杂性与吞吐量的考虑,多数团队不会选择类似,甚至的分布式事务。一个简单的就足以解决多数问题。当然,如果系统都是自家可控的,也是不错的选择
基本原理
放弃强一致性,实现最终一致性
不要了,就可以,系统间的数据在某个时间窗口可能是不一致的,通过添加一些补偿机制,来保证数据最终能够一致。只要数据的消费者感知不到这份不一致,系统总是可用的,就没有任何差异。举个不很恰当的例子,同事给你转了一万块,输完密码后的1秒内,其实银行扣除了他一万块,但并没有在你的账面上增加一万块(此时数据就是不一致的)。但1秒后,你的账面上到账一万块,此时数据最终一致了。作为数据的使用方,这1秒的不一致,你基本是感知不到的,最终系统的账面还是平的,于是乎,实现了子系统间的数据一致。
实现的关键点
接口幂等性
考虑到宕机,服务异常,网络闪断等不可控因素,依赖的第三方接口需要支持幂等性。所谓幂等性,指接口支持重入,同等条件下,不管调用你几次,所产生的影响都应该是相同的。譬如说,使用支付接口转账,不管调用多少次,只要支付ID不变,都只会产生一次转账。
DEMO
用户提现,需要扣除本地系统余额,并调用第三份支付接口
begin transaction //扣除本地库的余额 //并生成一个全局唯一的支付ID(1) pay_id = deducted_balance() //MQ or 入库一条日志 //记录本次扣除操作,并标记为扣除中(2) message_id = queue_message()end transaction//使用(1)生成的全局ID进行支付res = pay(pay_id)//支付结果一般会异步响应//如果有同步异常,则按需重试//【【== 此处应注意重用pay_id ==】】if should_retry(res) re_pay(pay_id)
~
//处理异步响应支付结果//支付成功,更新(2)生成的消息状态if ok update_ok(message_id) return//支付失败,调用第三方支付查询接口//二次校验是否没有支付res = query_pay_state(pay_id)//的确没有转账成功,回滚事务,把扣除的账补回去if error begin transaction rollback_banlance(pay_id) update_error(message_id) end transaction return//其实支付成功了update_ok(message_id)
~
//考虑到异步回调的时延等问题,一般还会开个worker//定时去捞取(2)中的消息,并主动查询支付结果
~
//以上仅为伪代码,忽略许多细节//譬如似涉及到金额的系统//应提供足量的日志,监控异常,定时核账
MORE
拾零散记公众号