最终一致性之TCC方案的执行流程 博客分类: architecture 最终一致性TCC
TCC是最终一致性的一个常见方案, 也是最简单的一个. 其他的方案如本地事件表, 本质上都是TCC的变种, 只是把confirm和cancel的时间往后移了而已.
事件表是更优雅的方案, 如果用消息队列来实现, 事件驱动架构的话, 想想都很美, 但是对于公司的遗留系统而言重构幅度较大; 相对而言TCC的引入不会遇到那么多大的阻力. 事件表方案如果出了问题, 比如消息队列崩了, 则系统就彻底歇菜. 而TCC即便没有效果, 也不会让事情变得更糟.
TCC的好处是实时性高, 不足之处是如果日志记录得不好, 则可能出现永远也无法恢复的数据一致性问题. 所以TCC方案一定要重视日志的作用.
说一个简单的TCC案例. 想象我们去电商网站购物, 点击结算的时候, 会生成一个订单, 并清空购物车, 这里就可能有出现一致性问题.
在上图中, shoppingcart和order显然是两个不同的数据库. 所以不能直接操作order数据库, 只能通过调用OrderService来实现订单的创建, 查询和取消. OrderService在分布式环境是不可靠的, 所以可能出现:
1. 创建订单成功, 但购物车清空可能失败.
和通常大学教材上说的不同, 现实世界中没有几个公司是真正地把购物车完全放到session中的; 而是要将购物车的数据持久化的, 通常是关系数据库或者键值对数据库.
所以清空购物车其实也是数据库操作, 有可能失败, 此时订单已经创建成功, 购物车却还没有清空, 用户将会感到不安, 会纳闷刚才下的订单是不是没有真正成功.
2. 创建订单失败, 但购物车却被清空了.
清空购物车里的结算项目被清空, 导致用户还要重新添加一次购物车. 这时用户可能就要投诉了.
如果用Spring框架来实现上述流程, 则TCC的相关代码只需要负责try-confirm-cancel, 不要显式地提交或者回滚事务. 如果要回滚, TCC代码直接抛出异常就行了, 事务管理器捕获到异常后会自动回滚. 所以TCC的实现代码不要试图直接操纵事务.
在上图中, empty shoppingcart或者confirm时, 出错, 直接抛出异常就好了, 当前线程的事务管理器会完成本地事务的回滚.
cancel的时候, 无论成败都要记录日志. 如果日志是记录到数据库里, 则必须在一个新的事务里往数据库写日志.
由于Spring JpaTransactionManger不支持REQUIRE_NEW类型的事务, 则需要在一个新的线程里写日志, 此时最好用消息队列比如Kaffka, RabbitMQ来支持日志的写入.