最终一致性之TCC方案的执行流程

TCC是最终一致性的一个常见方案, 也是最简单的一个. 其他的方案如本地事件表, 本质上都是TCC的变种, 只是把confirm和cancel的时间往后移了而已.

事件表是更优雅的方案, 如果用消息队列来实现, 事件驱动架构的话, 想想都很美, 但是对于公司的遗留系统而言重构幅度较大; 相对而言TCC的引入不会遇到那么多大的阻力. 事件表方案如果出了问题, 比如消息队列崩了, 则系统就彻底歇菜. 而TCC即便没有效果, 也不会让事情变得更糟.

TCC的好处是实时性高, 不足之处是如果日志记录得不好, 则可能出现永远也无法恢复的数据一致性问题. 所以TCC方案一定要重视日志的作用.

说一个简单的TCC案例. 想象我们去电商网站购物, 点击结算的时候, 会生成一个订单, 并清空购物车, 这里就可能有出现一致性问题. 



 

在上图中, shoppingcart和order显然是两个不同的数据库. 所以不能直接操作order数据库, 只能通过调用OrderService来实现订单的创建, 查询和取消.  OrderService在分布式环境是不可靠的, 所以可能出现:

1. 创建订单成功, 但购物车清空可能失败. 

扫描二维码关注公众号,回复: 483851 查看本文章

     和通常大学教材上说的不同, 现实世界中没有几个公司是真正地把购物车完全放到session中的; 而是要将购物车的数据持久化的, 通常是关系数据库或者键值对数据库.

     所以清空购物车其实也是数据库操作, 有可能失败, 此时订单已经创建成功, 购物车却还没有清空, 用户将会感到不安, 会纳闷刚才下的订单是不是没有真正成功.

 2. 创建订单失败, 但购物车却被清空了.

     清空购物车里的结算项目被清空, 导致用户还要重新添加一次购物车. 这时用户可能就要投诉了.

如果用Spring框架来实现上述流程, 则TCC的相关代码只需要负责try-confirm-cancel, 不要显式地提交或者回滚事务. 如果要回滚, TCC代码直接抛出异常就行了, 事务管理器捕获到异常后会自动回滚. 所以TCC的实现代码不要试图直接操纵事务.

在上图中, empty shoppingcart或者confirm时, 出错, 直接抛出异常就好了, 当前线程的事务管理器会完成本地事务的回滚.

cancel的时候, 无论成败都要记录日志. 如果日志是记录到数据库里, 则必须在一个新的事务里往数据库写日志.

由于Spring JpaTransactionManger不支持REQUIRE_NEW类型的事务, 则需要在一个新的线程里写日志, 此时最好用消息队列比如Kaffka, RabbitMQ来支持日志的写入. 

猜你喜欢

转载自rickgong.iteye.com/blog/2369662