2.2. 可靠事件投递的两种实现
2.2.1 本地事件表
本地事件表方法将事件和业务数据保存在同一个数据库中,使用一个额外的“事件恢复”服务来恢复事件,由本地事务保证更新业务和发布事件的原子性。考虑到事件恢复可能会有一定的延时,服务在完成本地事务后可立即向消息代理发布一个事件。
微服务在同一个本地事务中记录业务数据和事件数据
微服务实时发布一个事件关联业务服务中,如果事件发布成功立即删除记录的事件,这样能够保证事件投递的实时性。
事件恢复服务定时从事件表中恢复未发布成功的事件,重新发布,重新发布成功后删除记录的事件,这样能够保证事件一定能够被投递。
这样能够很好的解决上面提到的网络IO异常和服务器宕机的问题,但是业务系统和事件耦合在一起,额外的事件数据操作给数据库带来压力,也成为异步事件机制的一个瓶颈。
2.2.2 外部事件表
针对本地事件表出现的问题,提出外部事件表方法,将事件持久化到外部的事件系统,事件系统需提供实时事件服务以接收微服务发布的事件,同时事件系统还需要提供事件恢复服务来确认和恢复事件。
业务服务在事务提交前,通过实时事件服务向事件系统请求发送事件,事件系统只记录事件并不真正发送
业务服务在提交后,通过实时事件服务向事件系统确认发送,事件得到确认后事件系统才真正发布事件到消息代理
业务服务在业务回滚时,通过实时事件向事件系统取消事件
事件系统的事件恢复服务会定期找到未确认发送的事件向业务服务查询状态,根据业务服务返回的状态决定事件是要发布还是取消
该方式将业务系统和事件系统独立解耦,都可以独立伸缩。但是这种方式需要一次额外的发送操作,并且需要发布者提供额外的查询接口,这样就增加了系统实现的复杂性。
3. 幂等性
3.1 事件本身具备幂等性
本身具备幂等性的事件,需要考虑执行顺序。如果事件本身描述的是某个时间点的状态,而不是变化,那么就说这个事件具备幂等性。比如,某个时间点账户余额为100,这个事件就具备幂等性;某个时间点账户余额增加10,这个事件就不具备幂等性。
那么,这种具备幂等性的事件需要考虑执行顺序,比如,事件1是2016-07-16 15:07:31账户余额是100,事件2是2016-07-16 25:07:31账户余额是120。
如果事件1执行完成后执行事件2,将获得我们期望的结果。
如果事件2先执行,然后又执行了事件1,那结果就不是我们期望的了。
如果事件1执行完成后执行事件2,此时结果是我们需要的,但由于事件重复发送,又执行了一遍事件1,此时结果也不是我们期望的了。
简单的说,我们需要保证事件2一旦处理,事件1就不能再处理。
为了保证事件的顺序,最简单的做法就是在事件中添加时间戳。微服务记录每个事件最后处理的时间戳,如果收到的事件的时间戳早于我们记录的,丢弃该事件。当然,在高并发的情况下,同一时间内可能出现多个事件;事件由不同服务器发出,时间可能不同步。这两种情况下,可以选择使用全局递增序列号替换时间戳。
3.2 事件本身不具备幂等性
对于本身不具有幂等性的事件,主要思想是存储每条事件执行结果。当收到一个事件时,我们需要根据事件的标识ID查询该事件是否已经执行过,如果执行过直接返回上一次的执行结果,否则调度执行事件。
这里唯一需要考虑的就是资源开销:重复执行一次的开销,查询执行结果的开销。
如果重复执行一次的开销非常小,或者只有很少的事件会被重复接收,可以选择重复执行一次事件,在将事件持久化到的过程中,由于唯一键(标识ID)重复,持久化过程失败。
如果重复执行开销较大,则直接使用一个过滤服务,过滤重复事件。即使用标识ID过滤事件是否重复。如果是,直接返回上一次执行结果。
对于重复执行开销比较大的情况,可能服务执行时间较长。就会出现这么一种情况:接收到一个新的事件,服务开始执行,执行过程中,又接收到重复事件,这个时候上个事件还没有执行完成,即过滤服务还没有收到上次执行的结果,但是重复执行开销又大。解决办法就是对处理过程分段:接收、开始处理、处理完毕等,可以根据不同业务进行不同的分段。这样过滤服务就能够及时发现重复事件,并能够根据事件处理状态做出不同处理。
另一个需要注意的地方就是,微服务实现数据一致性最好的方式是最终一致性。有些需要考虑的极端情况下,是需要人工接入的,这里就不展开了
转载于:https://blog.51cto.com/17099933344/1925370