结合现有分布式系统的数据一致性思考
青栗 人气:1背景
我们项目本身分成了多套系统,但数据上有要求一致性的地方(比如订单状态,通俗点讲就是系统A更新了订单状态为状态一,那么系统B也需要把相同订单的订单状态更新成状态一,这样可以让我们不管是读系统A还是系统B的同一个订单的状态,都可以读出相同的状态数据)。
最原始的伪代码大概为:
sysA.db.transaction.begin();
var success = sysA.db.transaction.updateOrderStatus();
if(!success){
sysA.db.transaction.rollback();
return;
}
success = httpclient.post(sysB.url);
if(!success){
sysA.db.transaction.rollback();
return;
}
sysA.db.transaction.commit();
看了这个代码后,大家应该知道这里有个问题是调用系统B接口,可能出现网络或超时等问题,所以你是不知道系统B是执行成功了还是失败,如果他是执行成功了的,那么系统A回滚了就出问题了,因为两边数据就不一致了。
那么基于CAP理论的实现版BASE,我们采用数据最终一致方案。关于CAP跟BASE,大家可以参考这里
然后我这里参考了个别文章跟同事想法,思考了其中两个解决方案
简单解决方案一
异步任务处理,由原来同步调用B系统变为异步调用。
系统A先写数据库更新订单状态,然后在本地同时新建一个任务(任务初始状态为待执行),当调用B系统接口完成之后该任务改为已经执行,修改订单状态跟建立任务需要在一个数据库事务下。
后台定时脚本来执行待执行状态的任务。
如果异步调用系统B接口返回失败,则需要对之前订单状态更新进行回退。
如果异步调用系统B接口遇到网络问题或者超时,则考虑重试机制,注意重试机制要避免重复提交,可采取在系统A重试前确认和在系统B保证接口的幂等。
想了一下,这个方案有个问题就是状态可能发生了多次改变,如果先后顺序出现问题,那么将造成订单状态更新错误,所以就得有个队列来保存订单状态的多次先后顺序更新才行,于是有了方案二
简单解决方案二
引入消息队列,相当于对方案一的升级版,新建立任务变成新建立消息
系统A为消息生产者,系统B为消息消费者。
生产者系统A接收到用户请求,先写数据库更新订单状态,然后写一条更新订单状态的消息到消息队列,并且要新建一张消息状态表用于记录消息的执行状态(初始状态为待执行),以上三个操作要在同一个本地事务中进行,这个是容易忽略的地方。其实也可以在业务订单表增加一个字段用来表示消息执行状态。当然对于这个订单的后续业务操作,只有在这个消息执行成功后才能继续,也就是有一个因果先后关系在里面才行。
消费者系统B取出一条消息,进行相同订单的状态更新,处理完成后需要告诉系统A消息执行结果,是成功还是失败。这里简单说下失败的情况,第一次失败的话,系统B就可以进入重试逻辑,重试多次如果还是失败才需要告诉系统A我这边最终执行失败了,你可能要采取点措施才行,比如回滚呀,还是什么的。
总结
- 分布式系统以基本可用跟快速高效的最终一致为目标
- 远程RPC调用中的网络跟硬件等问题是造成一致性的主因,异步解耦加消息队列等方式可作为分布式系统满足最终一致性的一个比较好的方案。
参考:
- https://www.cnblogs.com/kerwing/p/9098893.html 主要参考了此文,然后做了一些自己的理解跟修改
- https://www.cnblogs.com/frankltf/p/10374662.html
- https://www.cnblogs.com/hzmark/p/consistency_model.html
加载全部内容