同步数据是微服务拆分的一大难点,尤其是在有历史包袱的情况下,我曾“深受其害”。那么理想情况下怎么做呢?
理想情况下
微服务的设计时,就应该尽量避免数据共享,不同的服务有不同的数据库,服务之间的数据是低耦合的,服务内部数据是高内聚的。如果需要把一个数据在多个服务之间,复制来复制去,那是不是设计出了问题?
数据随着领域模型的划分而被拆开,但仍有一些业务需求,如分析类业务,通常涉及多个服务的数据该怎么办?
借用并发编程中的一句名言Don't communicate by sharing, share by communicating.
这句话原义是指多个线程/纤程之间不共享内存数据,用互相通信来共享数据。这句话放在微服务系统中同样合适,需要多个服务的数据,那就调用多个服务的接口来获取,不要共享数据库,也不要把数据副本放到关联性不强的其他服务中。一旦数据副本放到另一个Microservice,也就意味着该数据与之强耦合了,必然要解决数据一致性的难题,Scalability也受限。
题主说的Event Bus做数据同步没问题的,但基于理想状态下“不应该把相同数据在服务间到处同步”的思路,Event Bus更适合作为CQRS模式的实现组件,每个服务订阅Topic,从Event数据中取出自身领域模型相关的实体/值对象,追加到自己的数据库中。
理想很丰满,现实很骨感
回到现实,大部分系统是有历史包袱的,没办法照本宣科,完全按照DDD的理论来,现实中数据同步非常常见,甚至一些时候为了向性能妥协,选择共享缓存、共享数据库这类“反模式”。
如果一定要在服务之间同步数据,数据库是MySQL的情况下,阿里开源的Canal
Canal把自己伪装成MySQL的Slave,读取并解析binlog,把事件抛到消息队列、或把数据存到其他地方。
这样做的好处是既高效又灵活,能很好的支持同步成异构数据。
如果在业务层抛Event出来处理,性能肯定不如binlog;如果数据库做多个从库,又没法做到“把数据A变成数据B”这种异构同步。
在微服务场景,每个服务关注点不一样,需要的数据结构通常也不一样,大多数情况是异构同步,同构同步往往出现在异地多活这类场景(比如otter就更适合做同构数据的异地同步)。
至于数据量大的问题,同步到另一个数据库,必然有时间和空间的开销,用binlog方式只能提高性能从而减少时间开销,但没法减少空间开销。减少空间开销的办法就是上面提到的两类:Share Nothing:Service B要数据A,就发请求找Service A去取;
Share DB:Service B直接读写与Service A相同的数据库(理论上不建议这样做)。
总结一下,理想状态应该尽量把属于同一个领域模型的数据,内聚到同一个数据库、同一个服务中;但现实中不得不做同步的时候,可以选择更底层方案减少时间开销,开源方案减少开发成本,Canal就是一个不错的例子。
我会持续分享一些DevOps和微服务相关的一些原创内容 ,欢迎点赞、关注。
参考