mavenJAVA面试

分布式事务--TCC优化方案

2018-08-10  本文已影响156人  可笑可乐

方案核心:TCC+事务消息+异常日志+人工介入

1 前言

分布式事务是分布式领域一大神坑。
架构即取舍、衡量利弊,世上没有完美的方案。无需过多转牛角尖,无需纠结,以免进入死胡同。

1.1什么是分布式事务

简单的讲,N个服务调用,要么同时成功,要么同时失败。
简单的讲,垮N个库,要么同时成功,要么同时失败。
数据库事务-》强一致性
分布式事务-》性能、可用性方面的考量,最终一致性
分布式环境中通信存在三种状态:success failure timeout,超时是最麻烦的
最基本要求:事务参与者要实现幂等性,简单的讲,就是多次提交依然能够达到同样的目的,数据状态的预期都是一致的
怎么实现幂等性?说白了就是去重,可借助业务id来进行校验,可用的工具有redis、db

1.2什么场景下需要分布式事务

能不用尽量不用。

2 实现分布式事务的几种方案

2.1 2PC

数据库层面,强一致性

第一阶段:协调者向所有事务参与方询问,都可以提交吗?所有参与方锁定资源,如果全部say yes,则想所有参与方发布提交命令。否则发布rollback命令。
第二阶段:大家一起提交或回滚

同步问题:大家都处于阻塞状态,协调者挂了,故障转移,需要阻塞的时间更长
超时问题:如果参与方超时了,还没接收到协调者的指令怎么办?傻傻的原地等待?这样也可能导致不一致

2.2 3PC

为了解决2PC阶段存在的超时、宕机等问题,加入一个precommit阶段,如果超时,则自动提交。方案基本用不到,不过多啰嗦展开。

2.3 TCC

Try-Confirm-Cancel的简称,是一种达到最终一致性的补偿性事务,跟2PC有点像,TCC针对的是业务层面,并非数据库层面。

在try阶段只是保留资源,执行业务锁定;
confirm阶段:当发起者收到所有请求者try success应答,向协调者发出confirm。

如果出现超时,或者failure响应的,则发出cancel指令。

协调者负责统一协调所有事务参与者进行confirm、cancel指令。

缺点:状态复杂,导致在业务开发过程中,复杂度上升,状态处理更麻烦。
协调者是一个关键角色,怎么保证协调者是高可用?

2.4 saga

image

该中心节点,即协调器知道整个事务的分布状态,相比于无中心节点方式,该方式有着许多优点:
能够避免事务之间的循环依赖关系。
参与者只需要执行命令 / 回复 (其实回复消息也是一种事件消息),降低参与者的复杂性。
开发测试门槛低。
在添加新步骤时,事务复杂性保持线性,回滚更容易管理。因此大多数 saga 模型实现均采用了这种思路。
总结一下:SAGA 模型的优点在于其降低了事务粒度,使得事务扩展更加容易,同时采用了异步化方式提升性能。但是其缺点在于很多时候很难定义补偿接口,回滚代价高,而且由于 SAGA 在执行过程中采用了先提交后补偿的思路进行操作,所以单个子事务在并发提交时的隔离性很难保证。

2.5 事务消息

rocketmq4.3支持事务消息,简单的讲,就是保持本地事务与消息发送的原子性。再简单的讲,就是我本地事务做完了,消息一定100%发送出去。

1、发送方:先往half队列发prepare消息
2、发送方:执行本地事务
3、发送方:如果commit,就发生commit消息,下发给订阅者;如果rollback就发生rollback消息,删除prepare,不下发。
4、rocketmq:如果没有接收到任何信息,可能超时啦,出了各种异常,咋办?回查事务状态,有可能发送方实例已经宕机,需要回查同一个生产者组的其他实例来获取状态,具体怎么获取?参考rocketmq的事务消息示例代码即可。
5、consumer段消息成功机制保障
对于消费者集群执行本地事务失败的情况,阿里提供给我们的解决方法是:人工解决
按照事务的流程,因为某种原因事务失败,那么需要回滚整个流程。如果消息系统要实现这个回滚流程的话,系统复杂度将大大提升,且很容易出现Bug,估计出现Bug的概率会比消费失败的概率大很多。

事务消息一定程度上也实现了分布式事务,适用于上游业务不需要下游响应的场景。发给事务消息就完事了。订阅者根据消息做下游业务。失败时人工介入。

2.6 分布式数据库

解决分布式事务的最好方案就是不解决,交给分布式数据库,天下大事,分久必合合久必分。以前大家费了老大劲把数据库拆开,现在技术条件逐渐成熟。陆续出现了几个分布式数据库,首推google的spanner全球分布式数据库,天然支持分布式事务。
基于google F1 spanner论文实现的分布式数据库有:TIDB、cockroachdb发展的都不错,1w+star可以持续关注,在国内也有诸多落地实现的。

3 TCC方案详解

3.1 角色

发起者、协调者、参与者


tcc.png

在上述例子中,bookingprocess即为发起者A,swiss B、easyjet C为参与者。

3.2 各个角色职责

发起者负责try,根据结果,向协调者发送confirm、cancel指令。

参与者实现try、confirm、cancel三个接口;
实现幂等性;
超时自动cancel。

协调者保证高可用,接收发起者的指令,根据情况发起confirm 、cancel,失败重试。

3.3 主流程

1、外部前端调用booking A,A调B\C服务的try接口,
2、B\C服务返回response给A,有几种情况:所有响应均为success;响应含有failure;B、C调用失败,不可用,或者超时。
3、如果所有请求均为success,则向协调者发出confirm指令,大家一起提交。消息中包含了B\C服务的confirm URL。
4、如果响应包含failure,则向协调者发出cancel指令,大家一起回滚。

4 TCC进化

4.1 TCC的缺陷

1、协调器如何保证高可靠,怎么解决协调器的单点故障?怎么解决服务宕机、网络抖动,超时等问题
2、开发复杂,参与者需要提供try、confirm、cancel接口
3、如果所有的可靠性措施都失灵了,还是有异常情况,怎么处理?
4、resttcc对接口做了一些约束,并不符合实际要求,例如用httpcode来标识各种状态,而很多项目团队一般在数据包中定义返回状态。

4.2 rocketmq事务消息登场

TCC方案中的交互模式皆为rest接口,在性能、可靠性方面不如消息的发布订阅模式,rocketmq4.3支持事务消息,那么我们可以让rocketmq担任协调者的角色。
当然你首先要先能玩转rocketmq,能保证高可用,不是随便搭个玩玩就行了。

怎么改进?
一、try阶段还是rest调用。
事务发起者发送两条事务消息一条消息体为commit,另一条为cancel,在接收到所有参与者的response后,根据返回的状态进行相应处理:
1、如果所有的参与者都返回success(怎么样标识success大家遵循统一的约定),标识“commit”事务消息的状态为commit,cancel直接丢弃即可
2、如果含有failure,则标识“cancel”事务消息的状态为commit,则cancel事务消息生效,另一台直接丢弃
3、如果超时了,或者发起者当前实例宕机等等异常因素,这时候不用处理都可以,大家想想为什么?

二、confirm阶段
各个参与者无需实现confirm、cancel restfu接口
需要去订阅事务消息队列,监听commit或者cancel消息

如果订阅到commit消息,那就是标识try阶段,大家都已经成功锁定资源了。那就大家都来commit吧

流程走到这里,大家都已经锁定资源的前提下,commit出现异常的概率已经非常低了。
但是如果真的出现异常,commit失败怎么办?怎么办?怎么办?
再通知发起者,再发起cancel消息,再大家一起cancel,如果有人cancel失败怎么办?怎么办?怎么办?
死循环了,果断跳出这个定时思维,这时候需要记录异常日志,人工介入处理。不要什么都自动化,架构即取舍,孰重孰轻,掂量一下就知道了。
三、异常日志体系
服务如果commit失败,或者超时自动cancel等等,都记录异常日志到业务数据库,同时把异常日志推送到kafka->elk,结合链路监控日志、应用日志、业务异常日志进行综合处理,人工介入。

四、cancel阶段
如果订阅到cancel消息,那就表明发起者让大家一并cancel,释放资源,做业务补偿处理
cancel完,把日志推送到kafka-》elk-》人工综合分析

4.3 异常日志体系的建立

方案可以采用业务异常日志(业务DB存储一份)+kafka+ELK。
关联聚合分析:根据traceid进行聚合分析,异常日志+链路监控+应用日志。

以便人工介入做最后的一环保障

参考资料

TCC英文文档、github开源TCC方案、ACID、CAP原理、rocketmq4.3事务消息的实现机制https://mp.weixin.qq.com/s/rE6l2iWY2lGxDCcog7Muvw

上一篇下一篇

猜你喜欢

热点阅读