关于sharding-jdbc分布式事务:
Best efforts delivery transaction (已经实现).
Try confirm cancel transaction (待定).
Sharding-JDBC由于性能方面的考量,决定不支持强一致性分布式事务。
最大努力送达型事务说明
Best efforts delivery transaction就是最大努力送达型事务。在分布式数据库的场景下,相信对于该数据库的操作最终一定可以成功,所以通过最大努力送达,反复尝试。
很少有公司分布式场景下用强一致性事务。就笔者和陆金所朋友沟通,他们保证数据一致性的核心是T+1对账。再比如带有NFC功能的手机可以给公交卡充值的场景,笔者做过试验,在微信支付扣费后,将公交卡挪开,这时候会导致充值失败,但是资金已经扣除。微信支付的做法是T+1对账后将资金返回给用户,微信支付并不会在充值失败后一段时间内马上将资金返回给用户。
最大努力送达型事务架构图
最大努力送达型事务的架构图
摘自sharding-jdbc使用指南☞事务支持:http://shardingjdbc.io/1.x/docs/02-guide/transaction/
解读这张架构图,对几个重要的执行过程进行更详细的说明:
执行前。执行前事件->记录事务日志。sharding-jdbc对于任何执行,都会先记录事务日志。
执行成功。执行结果事件->监听执行事件->执行成功->清理事务日志。如果执行成功,就会清理事务日志。
执行失败,同步重试成功。执行结果事件->监听执行事件->执行失败->重试执行->执行成功->清理事务日志。
执行失败,同步重试失败,异步重试成功。执行结果事件->监听执行事件->执行失败->重试执行->执行失败->"异步送达作业"重试执行->执行成功->清理事务日志
执行失败,同步重试失败,异步重试失败,事务日志保留----如图所示,执行结果事件->监听执行事件->执行失败->重试执行->执行失败->"异步送达作业"重试执行->执行失败->… …
说明:sharding-jdbc在执行前都会通过执行前事件来记录事务日志;执行事件类型包括3种:
BEFORE_EXECUTE;
EXECUTE_FAILURE;
EXECUTE_SUCCESS;
同步
另外,这里的同步不是绝对的同步执行,而是通过google-guava的EventBus订阅执行事件,在监听端判断是EXECUTE_FAILURE事件,然后最多重试次。后面对的源码分析有介绍;
异步
这里的异步通过外挂程序实现,本质就是一个基于elastic-job的分布式JOB任务,在下一篇文章会有分析;
使用限制
使用最大努力送达型柔性事务的SQL需要满足幂等性。
INSERT语句要求必须包含主键,且不能是自增主键。
UPDATE语句要求幂等,不能是UPDATE table SET x=x+1。
DELETE语句无要求。
开发示例
备注:SoftTransactionConfiguration支持的配置以及含义请参考官方文档sharding-jdbc使用指南☞事务支持:http://shardingjdbc.io/docs/02-guide/transaction/,这段开发示例的代码也摘自官方文档;也可参考模块中如何使用柔性事务,但是这里的代码需要稍作修改,否则只是普通的执行逻辑,不是sharding-jdbc的执行逻辑:
核心源码分析
通过sjdbc源码之路由&执行中对ExecutorEngine的分析可知,sharding-jdbc在执行SQL前后,都会调用发布执行事件。那么调用的地方,就是柔性事务处理的地方。而sharding-jdbc在中调用了初始化注册事件,所以柔性事务实现的核心在SoftTransactionManager这里。
柔性事务管理器
柔性事务实现在中,核心源码如下:
从这段源码可知,柔性事务的几个重点如下,接下来一一根据源码进行分析;
事务日志存储器;
最大努力送达型事务监听器;
异步送达JOB任务;
1.事务日志存储器
柔性事务日志接口类为,有两个实现类:
RdbTransactionLogStorage:关系型数据库存储柔性事务日志;
MemoryTransactionLogStorage:内存存储柔性事务日志;
1.1事务日志核心接口
TransactionLogStorage中几个重要接口在两个实现类中的实现:
void add(TransactionLog):Rdb实现就是把事务日志TransactionLog 插入到表中,Memory实现就是把事务日志保存到中;
void remove(String id):Rdb实现就是从表中删除事务日志,Memory实现从中删除事务日志;
void increaseAsyncDeliveryTryTimes(String id):异步增加送达重试次数,即TransactionLog中的asyncDeliveryTryTimes+1;Rdb实现就是update 表中字段加1;Memory实现就是TransactionLog中重新给asyncDeliveryTryTimes赋值;
findEligibleTransactionLogs(): 查询需要处理的事务日志,条件是:①,②,③,每次最多查询参数size条;Rdb实现通过sql从transaction_log表中查询,Memory实现遍历ConcurrentHashMap匹配符合条件的TransactionLog;
boolean processData():Rdb实现执行TransactionLog中的sql,如果执行过程中抛出异常,那么调用increaseAsyncDeliveryTryTimes()增加送达重试次数并抛出异常,如果执行成功,删除事务日志,并返回true;Memory实现直接返回false(因为processData()的目的是执行TransactionLog中的sql,而Memory类型无法触及数据库,所以返回false)
1.2事务日志RDB存储核心源码
事务日志RDB存储核心源码在中,主要提供了对事务日志表的CRUD接口。
1.3事务日志存储样例
transaction_log中存储的事务日志样例:
1.2最大努力送达型事务监听器
核心源码如下:
BestEffortsDeliveryListener源码总结:
执行前,插入事务日志;
执行成功,则删除事务日志;
执行失败,则最大努力尝试次;
1.3 异步送达JOB任务
同步重试若干次后,如果依然没有执行成功,可以通过部署的异步送达JOB任务继续重试,该特性将在下一篇文章详细讲解;
领取专属 10元无门槛券
私享最新 技术干货