微服事务处理方案 (分布式事务处理方案)
1. 什么是事务
由一组操作构成的可靠, 独立的工作单元.
事务具有以下特点:
•Atomicity(原子性)
•Consistency(一致性)
•Isolation(隔离性)
•Durability(持久性)
2. 事务的一致性
单体应用可以在数据库的事物管理器中获得强一致性, 这种本地事物可靠简单.
而在微服或者 SOA 的场景下, 我们的本地事物就不作用了. 对于分布式系统 Google 提出 CAP 定理 ,
分布式的事物只能同时拥有以下三项中的两个:
•Consistency(一致性): 所有 用户看到一致的数据.
•Availability(可用性): 总能找 到一个可用的数据复本.
•Tolerance to Network Partition(分区容忍性): 即使 在系统被分区的情况下, 仍然满足上述两点.
分布式系统的事物无法做到强一致性, 只能做到最终一致性.*
3. 常见分布式事物的处理方案
2pc 两段提交方案
3pc 三段提交方案, 是两段提交方案的进化.
TCC 模式 try ,confirm ,cancel.
可靠消息机制 , 可靠消息分为 非事物消息 和 事物消息.
事物消息, 简单的说就是消息投递成功, 你本地的数据库事物肯定提交成功, 消息投递失败你本地事物也肯定提交失败. 相当于你投递消息和操作数据库是绑定在一起的, 两者是在同一个事物中.
非事物消息, 简单的说就是消息投递成功, 本地数据库事物不一定执行成功.
而现有的开源的 MQ 框架 大多数是不支持 事物消息的, 也没有和本地事物进行配合.
RocketMQ 好像实现了这个事物消息功能, 有兴趣的同学可以去看看.
可靠消息机制保证数据一致性的解决方案 (非事物消息)
先来看个案例:
假设我有一个 文章微服 负责发布文章, 查询文章列表等, 还有一个 用户微服 保持用户的一些基本信息, 如: 用户发文章的篇数等.
现在发文章在 文章微服, 而用户的发文篇数在 用户微服. 这种情况下我们怎么保证 发文的计数 不会多也不会少, 保证其的一致性的呢?
大家先来看一段代码:
假设这是 文章微服 发文代码:
- @Transactional
- public void saveArticle(Article article){
- try{
- // 保存文章
- articleDao.saveArticle(article);
- MqEvent saveArticleEvent = new MqEvent();
- // 消息 ID
- saveArticleEvent.setMsgId(UUIDUtil.mongoObjectId());
- // 发送保存文章 事件个用户服务
- sender.sendAddArticleMqEvent(saveArticleEvent);
- }catch (Exception e){
- // 抛异常 让事务回滚
- throw new RuntimeException();
- }
- }
- }
看起来很正常呀! 没有什么问题! 我们来列举一下可能会出现的情况;
保存文章正常, MQ 发送也正常, 好万事大吉, 数据都正常.
保存文章正常, MQ 发送失败, 抛异常. 这时数据回滚, 虽然出了点问题 但是数据正常. 这也没问题.
文章保存失败, 这时肯定抛异常, MQ 发送走不到, 这时也是正常的.
文章保存成功, MQ 发送成功, 这时会不会有问题出现呢? 刚刚我们说了 我们的 MQ 是非事物性的, 恰巧这个时候 MQ 回执异常导致 MQ 抛异常了. 本地事物回滚, 但是 MQ 虽然抛异常, 消息却发成功了. 这时候 会导致 用户发文计数多了一篇, 数据不一致了导致.
虽然这种做法看起来没有什么问题, 但其实是有问题的.
为了解决这个问题, 我们需要 增加一个本地事件表, 专门存放 MQ 事件 , 有定时器轮训去发送 MQ 消息, 发送成功后做标记, 或者删除. 很多 MQ 都会有 消息回执的. 当成功投递到消息队列是 就会得到回执.
于是我们的代码应该改成这样:
- @Transactional
- public void saveArticle(Article article) {
- articleDao.saveArticle(article);
- MqEvent saveArticleEvent = new MqEvent();
- // 消息 ID
- saveArticleEvent.setMsgId(UUIDUtil.mongoObjectId());
- // 保存事件到事件表
- mqEventDao.addMQEvent(saveArticleEvent);
- }
这样在本地事物的强一致性下可以保证, 发文的同事插入发文事件.
说说 用户微服那边的处理 , 用户微服也应该有一个这样的事件表, 保存接收的事件, 通过轮训等方式处理这些事件, 只有成功的接收了事件, 事件才能从 MQ 队列里面消失. MQ 在消费消息的时候如果遇到异常会重新将消失重新发回到队列中, 很多 MQ 具有这个特性.
细心的同学可能会想到我同一个事件消息投递了两次怎么办? 这就涉及幂等性的设计了, 看到上面的消息 ID 没? 这就是为幂等性设计的 唯一 ID; 当用户微服收到两个消息 ID 是一样的时候, 丢弃掉一个.
总结
我们的微服系统 只要涉及 增 删 改 的操作都应该通过可靠事件进行操作, 而不能直接通过 REST 接口, 去操作, 也不能直接的发送事件去操作. 对于消防端, 要做好幂等性的处理. 可靠事件处理微服的事物算是比较靠谱的做法, 2pc,3pc ,Tcc 在微服事物处理这一块, 基本上起不了什么作用.
本人的理解大概是这样, 欢迎大家提出疑问.
本文的重点是你有没有收获与成长, 其余的都不重要, 希望读者们能谨记这一点. 同时我经过多年的收藏目前也算收集到了一套完整的学习资料, 包括但不限于: 分布式架构, 高可扩展, 高性能, 高并发, Jvm 性能调优, Spring,MyBatis,Nginx 源码分析, Redis,ActiveMQ,,Mycat,Netty,Kafka,MySQL,Zookeeper,Tomcat,Docker,Dubbo,Nginx 等多个知识点高级进阶干货, 希望对想成为架构师的朋友有一定的参考和帮助
需要更详细思维导图和以下资料的可以加一下技术交流分享群:"708 701 457" 免费获取
来源: http://www.jianshu.com/p/f70716346d29