TCC 分布式事务复盘

Posted by 孙继峰 on May 4, 2020

近期刚完成分布式事务相关的开发与对接, 想借此机会做一次复盘.

目标回顾

我的目标

完成设计、保证其正常提供服务

组长的目标

跨服务之间数据一致性, 方便业务端调用, 并没有精确的把整个流程时间加在考量结果中, 只是时间能够接受即可, 要求数据一致性

结果评估

已完成设计, 已保证正常服务 已保证数据一致, 除非服务节点故障, 需要人工重试

流程梳理

TCC分布式事务实现概览

事务管理器实现

主要对外提供以下几个HTTP接口:

  • register: 业务端调用, 注册一个事务组
  • join: 服务A, 服务B, 服务N 调用, 加入一个事务组
  • confirm: 业务端调用, 令事务组内所有服务都提交预留资源
  • cancel: 业务端调用, 令事务组内所有服务都取消预留资源
  • retryConfirmUnit: 业务端调用, confirm 时可能会导致部分服务 confirm 成功, 用于重试 confirm
  • retryCancelUnit: 业务端调用, cancel 时可能会导致部分服务 cancel 成功, 用于重试 cancel

商品服务适配事务管理器

  • try: 业务端调用, 由于客户端重试, 需要实现幂等, 在此阶段将库存同步到Redis上, 在Redis上进行库存预扣, 保证速度
  • confirm: 事务管理器调用, 由于事务管理器重试, 需要实现幂等, 在此阶段扣除MySQL库存
  • cancel: 事务管理器调用, 由于事务管理器重试, 需要实现幂等, 在此阶段把预扣的库存加回到Redis上
    其中一个地方我个人认为我设计的不错, 就是把库存同步到Redis上, 这里借助了Redisson的CAS功能, 在Redis中没有数据的时候会查数据库, 然后CAS同步到Redis上, 如果Redis上有数据, 就不再重试, 如果Redis上没有数据再把数据同步到Redis中. 这样可以以Redis中的数据为基准进行修改了. 这样做的原因是这个接口调用频率不高, 不会突发.
    如果有突发的还可以新加一个同步中的状态, CAS设置同步中成功之后再查库, 在CAS把数据同步到Redis上
更优方案

使用开源事务管理器, 当初不使用开源的事务管理器是因为不能够跨语言, 但是现有开源的分布式事务已经支持HTTP协议了, 技术调研不足 (反思).

过程中踩的坑

mvn -test 导致数据库事务死锁(批量插入并更新相同的唯一索引导致)
@Async 导致循环引用(前置代理事务, 后置代理异步, 引用不同, 导致自检报错)
RedissonClient 与 RedisTemplate 不兼容(各个Client编码不同)