原文转自:EAII企业架构创新研究院《微服务架构下的数据一致性保证(二)》,行文结构和内容有修改。
在《微服务架构下的数据一致性:概念及相关模式》中介绍了在微服务中实现数据一致性的三种方式,包括可靠事件模式、业务补偿模式、TCC模式。本文重点说一下可靠事件投递。
可靠事件模式属于事件驱动架构,微服务完成操作后向消息代理发布事件,关联的微服务从消息代理订阅到该事件从而完成相应的业务操作,关键在于可靠事件投递和避免事件重复消费。
可靠事件投递有两个特性:1)每个服务原子性的完成业务操作和发布事件;2)消息代理确保事件投递至少一次(at least once)。
避免重复消费要求消费事件的服务实现幂等性。
现下流行的消息队列都已经实现了事件的持久化和at least once的投递模式,所以可靠事件投递的第二条特性已经满足,这里就不展开。需要着重说的就是可靠时间投递的第一条特性和避免事件重复消费,即服务的原子性和消费者的幂等性。
先来看一段简单的代码:
public void trans() {
try {
// 1. 操作数据库
bool result = dao.update(mode1);// 操作数据库失败,会抛出异常
// 2. 如果第一步成功,则操作消息队列(投递消息)
if(result){
mq.append(mode1);// 如果mq.append方法执行失败,会抛出异常
}
} catch (Exception e) {
roolback();// 如果发生异常,就回滚
}
}
根据上面代码和时序图,理想化的情况会出现3中情况:
之所以说是理想化,是因为上面的思路还是传统的本地事务,但是在微服务架构中,可能出错的情况更为复杂,其中最容易出现的错误就是网络IO和服务器宕机:
mq.append()
方法抛出异常,最终结果是事件投递成功,但是数据库被回滚。在单服务器情况下,上面提到的两种异常发生概览不大,但是在当前多服务器、网络情况复杂的环境中,发生的概率被大大放大,由于是异步处理,一旦问题发生,拍错将变得更加困难。
本地事件表方法将事件和业务数据保存在同一个数据库中,使用一个额外的“事件恢复”服务来恢复事件,由本地事务保证更新业务和发布事件的原子性。考虑到事件恢复可能会有一定的延时,服务在完成本地事务后可立即向消息代理发布一个事件。
这样能够很好的解决上面提到的网络IO异常和服务器宕机的问题,但是业务系统和事件耦合在一起,额外的事件数据操作给数据库带来压力,也成为异步事件机制的一个瓶颈。
针对本地事件表出现的问题,提出外部事件表方法,将事件持久化到外部的事件系统,事件系统需提供实时事件服务以接收微服务发布的事件,同时事件系统还需要提供事件恢复服务来确认和恢复事件。
该方式将业务系统和事件系统独立解耦,都可以独立伸缩。但是这种方式需要一次额外的发送操作,并且需要发布者提供额外的查询接口,这样就增加了系统实现的复杂性。
本身具备幂等性的事件,需要考虑执行顺序。如果事件本身描述的是某个时间点的状态,而不是变化,那么就说这个事件具备幂等性。比如,某个时间点账户余额为100,这个事件就具备幂等性;某个时间点账户余额增加10,这个事件就不具备幂等性。
那么,这种具备幂等性的事件需要考虑执行顺序,比如,事件1是2016-07-16 15:07:31账户余额是100,事件2是2016-07-16 25:07:31账户余额是120。
简单的说,我们需要保证事件2一旦处理,事件1就不能再处理。
为了保证事件的顺序,最简单的做法就是在事件中添加时间戳。微服务记录每个事件最后处理的时间戳,如果收到的事件的时间戳早于我们记录的,丢弃该事件。当然,在高并发的情况下,同一时间内可能出现多个事件;事件由不同服务器发出,时间可能不同步。这两种情况下,可以选择使用全局递增序列号替换时间戳。
对于本身不具有幂等性的事件,主要思想是存储每条事件执行结果。当收到一个事件时,我们需要根据事件的标识ID查询该事件是否已经执行过,如果执行过直接返回上一次的执行结果,否则调度执行事件。
这里唯一需要考虑的就是资源开销:重复执行一次的开销,查询执行结果的开销。
对于重复执行开销比较大的情况,可能服务执行时间较长。就会出现这么一种情况:接收到一个新的事件,服务开始执行,执行过程中,又接收到重复事件,这个时候上个事件还没有执行完成,即过滤服务还没有收到上次执行的结果,但是重复执行开销又大。解决办法就是对处理过程分段:接收、开始处理、处理完毕等,可以根据不同业务进行不同的分段。这样过滤服务就能够及时发现重复事件,并能够根据事件处理状态做出不同处理。
另一个需要注意的地方就是,微服务实现数据一致性最好的方式是最终一致性。有些需要考虑的极端情况下,是需要人工接入的,这里就不展开了。
主页:http://www.howardliu.cn/
博客:微服务架构下的数据一致性:可靠事件模式