现在互联网公司都在做微服务架构,我也有幸多次参与单体架构向微服务架构转变的工作中。在整个过程中,最为困难的便是如何在迁移的时候如何保证数据的一致性。今天回想起之前做的事情,觉得还是应该留一些方法论,以便之后再次用到。
首先,我们回顾一个比较常见的业务场景:
在电商系统中,用户会在订单系统中创建一个订单,对应的就是在 数据库 order 中写入一条数据。与此同时,众多的周边系统也需要这份订单数据。基于 Single Source of Truth (单一真实数据源) 这样的设计理念。其他服务需要获取订单数据,只有两种方式:
- 不间断的调用订单服务接口,来获取订单数据
- 订单服务进行数据推送
在业务场景中是推模式还是拉模式,基于我之前的经验,我是比较主张使用推模式,因为如果使用拉模式,主要有两个比较头疼的痛点:
- 获取到的数据存在一定的延迟性
- 如果N多的服务都在频繁拉取,会对订单数据库的性能造成影响,严重时还可能会把订单数据库给拉挂,不幸的是我在生产环境中确实碰到过....
因此基于上面的这个场景,我们可以将其抽象出一个特定的业务模型:在微服务中如何进行数据纷发
有相关经验的同学,一定听过或者使用过 “双写” 这样的朴实的技术
@Transactional(rollbackFor = Exception.class)
public void insertOrderInfoAndSendMsg(){
// step1 : 插入数据
boolean insertResult = orderService.inster(order);
if(insertResult){
// step2 : 插入数据成功之后便发送mq数据
mq.send(order);
}
}
这样简单的代码,会存在典型的数据一致性的问题。简单说一个极端场景,由于网络抖动,在于MQ交互的时候,抛出了异常,导致数据库的数据回滚,但其实MQ已经发送成功,如何解?
下面说一下,我们之前的技术解决方案以及业内通用的解决方案
1. Transactional Outbox (事务性发件箱)
-
step1 : 系统在写入数据的时候,会同时往
order与order-outbox中都写入两条数据,由于这是在同一个事务中的,因此可以由Spring的事务管理机制来保证事务性的写入 -
step2 : 部署一个消息中转的服务(简化操作可以是一个JOB)来定时的拉取数据,获取到数据走便发送到MQ中,同时修改order-outbox中的数据状态为已发送。否则会进行重试操作
类似代码实现:github.com/dilaverdemi…
2. Change Data Capture (数据变更处理)
这样的处理逻辑,是利用了数据库的变更日志来进行数据的处理,业内有一些比较成功的开源项目,大家可以自行参考:
-
canal : github.com/alibaba/can…
-
debezium : debezium.io/
基本上以上两种方法都是比较常用的。
方案一的使用,逻辑上简单可控,开发团队甚至可以抽象出代码模型,来统一解决该类问题;但确实对代码有一定的侵入性与性能损耗;适用于中小规模吧。
方案二的使用,没有侵入性,比较实时且性能损耗低,但是会比较复杂一些,同时这类中间件的使用需要专门的团队来治理维护。
当然了,我也见过有的团队直接使用双写这样的粗暴步骤,然后会做一个后台程序进行数据对比与修正。因此,还是选择自己当前最为合适的技术方案吧。
2020.10.08
假期过的太快