存在的问题

按照目前公司产品中数据同步采用的发布订阅的规则,假设以下的场景:

  1. 在 A 系统中将某条学生记录 stu_0 做了修改:stu_0 -> stu_A1,并将修改后的记录发送到了 MQ 中。
  2. 在 B 系统接收到 MQ 中的 A1 之前,B 系统也将学生记录 stu_0 做了修改:修改为 stu_B1 并发送到了 MQ 中。
  3. 现在在 MQ 中有两条记录需要同步,按 FIFO 顺序为 stu_A1、stu_B1。
  4. A 系统接收到 stu_B1,做了修改:stu_A1 -> stu_B1。
  5. B 系统接收到 stu_A1,做了修改:stu_B1 -> stu_A1。
  6. C 系统接收到 stu_A1、stu_B1,做了修改:stu_0 -> stu_A1 -> stu_B1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-------------------
| A |
-------------------
| |
| stu_0 -> stu_A1 |
| |
-------------------
RabbitMQ
(-----------------)
==========> ( stu_B1 | stu_A1 )
(-----------------)
-------------------
| B |
-------------------
| |
| stu_0 -> stu_B1 |
| |
-------------------

最终,B 和 A、C 中的同一条记录内容并不相同,A、B、C 三个系统的数据出现了不一致的情况。

解决方案的思考

发起修改操作的系统订阅自身发送的数据

如果 A、B、C 系统都能够按照 MQ 中的数据的顺序去修改记录,那么该条记录最终在各个系统中都将是 stu_B1,即可以实现最终一致性。
这要求 A、B、C 系统都必须订阅 MQ 中的数据,包括自身系统发送到 MQ 中的数据

优点:可以实现数据的最终一致性。
缺点:

  • 在 RabbitMQ 中是否可以实现这样的配置?
  • 对于发起修改操作的系统来说,会重复执行一次更新操作。

对每条记录引入一个在各个系统中唯一的版本号

以上述场景为例,我们给学生记录添加一个全局的版本号,假设初始数据为 stu_0,数据同步流程如下:

  1. 先在 A 系统中修改数据:stu_0 -> stu_1
  2. 后在 B 系统中修改数据:stu_0 -> stu_2
  3. B 系统接收到 A 系统修改后的数据,和当前记录进行版本比对,发现 stu_2 > stu_1,所以丢弃该数据的修改。
  4. A 系统接收到 B 系统修改后的数据,和当前记录进行版本比对,发现 stu_1 < stu_2,对数据做了修改:stu_1 -> stu_2
  5. C 系统依次接收到 A、B 系统修改后的数据,对数据做了修改:
    1. 第一次版本比对发现 stu_0 < stu_1,对数据做了修改:stu_0 -> stu_1
    2. 第二次版本比对发现 stu_1 < stu_2,对数据做了修改:stu_1 -> stu_2

优点:在数据在各个系统同步时,可以避免一些过期数据的更新操作。
缺点:必须添加版本字段,并且要管理全局版本号,系统的复杂度和开发、测试工作量都会增加。

最终三个系统中该条记录均为 stu_2,保持了数据的最终一致性。