13.sjdbc之最大努力型分布式事务

关于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任务继续重试,该特性将在下一篇文章详细讲解;

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180913G1HBW300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券