转载请注明出处:http://www.cnblogs.com/lizo/p/8516502.htmlhtml
当单库已不能支撑当前业务的时候,咱们每每都考虑进行分库(横向拆分或者纵向拆分)。但分库有个没法回避的问题,就是事务问题。网上有不少分布式事务解决方案,例如XA,TCC等,可是最经常使用,也是改形成本最低就是使用最终一致性来保证分布式事务。
比较经常使用的就是使用消息中间件(RabbitMq,RocketMq),经过事务消息来解决最终一致性。参考https://zhuanlan.zhihu.com/p/25933039?utm_source=tuicool&utm_medium=referral。spring
本篇文章将使用数据库的来达到最终一致性的实现方案。数据库
注:如下有些内容是在使用事务消息(不管是基于数据库仍是基于消息队列)应该考虑的地方。网络
所谓基于数据库的事务消息,其实很好理解,就是在数据库中建立一个相似消息队列的表,用于保存事务消息。在拆分前,一个事务中,有多个主库的数据操做。以下图,负载均衡
可是在拆分数据库后,有业务被拆分到分库中去了,这样,原有的单库事务被打破,可是经过把拆分出去的业务使用一个事务消息来代替(事务消息表也是在主库中,因此这里仍是单库事务),后续再经过其余方式去执行该事务消息所对应的业务逻辑便可,这样,就能够达到最终一致性,以下图框架
前面说到了,事务消息须要一个处理器来进行执行事务消息所对应的业务逻辑。事务处理器应该是顺序的去读取并执行的。异步
设想一个场景:当出现某一条消息处理失败,若是执行器要等当前消息执行成功才继续日后执行(甚至该消息永远不会处理成功),那么会影响后续消息的执行,致使整个系统出现问题。分布式
所以,消息处理器即要保证消息处理尽量处理快,又能保证消息最终能执行成功。 在消息执行器中必须设置2个任务:ui
注:上图左边那个是消息队列及其处理状态spa
设想,若是分库业务执行成功(更新分库),而后去更新消息状态(主库),这样,又是一个夸库事务,因此,得想其余办法来避免,最简单的方法,就是在分库里面也建一个消息表,保存处理的成功的消息。这样,经过对比主库和分库的消息表,就知道哪些事务消息没有执行成功
前面介绍了,消息处理器的核心功能就:
为了完成上面功能,须要消息处理任务和消息校验任务,经过定时调度任务来触发这2个任务(例如,5s触发一次)
消息处理任务就是经过扫描待处理的消息,而后通知业务系统执行。
再次强调,消息处理任务不会管消息是否执行成功。都是按照消息队列表顺序执行下去。
校验任务就是比较主库和分库中的消息记录(主库中记录的全部消息,分库中记录的执行成功的消息),对执行未成功的消息发起重试,若是屡次重试失败则发出告警,须要人工介入。
使用消息中间件,通常都须要在代码中显示的编写提交中间件事务消息的代码,相似下面
public boolean transaction(String text){ try { 发送事务消息 执行本地事务 提交事务消息 return true; } catch (TmcException e) { return false; } }
但在实际项目中,事务的传播性的问题(spring 的事务注解是支持事务的传播性),就须要修改业务代码。但使用基于数据库的消息队列就没有这个问题
@Transactional public void publishAS(String text){ 执行本地事务逻辑 插入事务消息 }
因此在既有代码改造上(特别是复杂系统中),使用数据库的事务消息能够减小代码的改动
咱们知道,在使用消息中间件的时候,都须要实现一个回调接口,当事务消息长时间没有commit的时候,会调用该接口来确认是否须要commit(例如发送消息成功,可是在commit的时候网络不可用)。而基于数据局的事务消息队列就没有这个问题
基于数据库的事务消息也有一个比较明显的缺点:
基于数据库和基于消息队列的事务消息的基本思路都同样,使用最终一致性来避免分布式事务带来的额外系统复杂性和代码开销。基于数据库的事务消息在既有业务改造中,代码变更较小,也不须要额外的引入消息中间件,可是带来的问题就是对数据库更多的访问。而基于消息中间件的问题就是如何避免在与消息中间件交互的出现问题的时候如何应对。固然,以上只是我我的理解,若是系统有什么设计不合理或者有改进的地方,欢迎讨论。