关于服务间调用循环依赖的一些思考


#软件架构与思考


有哪些循环依赖场景?

场景1:

服务A 对外提供了接口 a1,a1 内部会调用 服务B 的接口 b1。 服务B 对外提供了接口 b2,b2 内部会调用 服务A 的接口 a2。

即:

A.a1 -> B.b1
B.b2 -> A.a2

这种依赖是 A 和 B 之间的循环依赖。

从某个角度来说,把 A、B 看做类,a1、b1、a2、b2 看做方法也是可以的。

场景2:

服务A 对外提供了接口 a1,a1 内部会调用 服务B 的接口 b1。 服务B 对外提供了接口 b1,b1 内部会调用 服务A 的接口 a2。

即:

A.a1 -> B.b1
B.b1 -> A.a2

这种依赖是 A 和 B 之间的依赖。与场景1不同的是,对A.a1的调用的路径是:

A -请求-> B -请求-> A -响应-> B -响应-> A

场景3:

服务A 对外提供了接口 a1,a1 内部会调用 服务B 的接口 b1。 服务B 对外提供了接口 b1,b1 内部会调用 服务A 的接口 a1。

即:

A.a1 -> B.b1
B.b1 -> A.a1

这种就是死循环了。服务上线就会一堆超时。

循环依赖可能带来的问题和解决方案

服务上线流程变的复杂

以 Dubbo 为例,Dubbo 中需要将协议以打包的形式暴露出去,让需要该服务的其他服务引用协议。

在有循环依赖的情况下,要考虑谁先打正式包,要不要引入临时包,谁先被引入,哪个服务先上线。

解决方案:

重新梳理,去除循环依赖,比如不需要循环依赖也能解决问题;也可以增加第3个服务来解除循环依赖。

数据库事务操作中可能引入脏数据

在实际业务中,事务的隔离级别是读已提交或者可重复读

以场景2为例子:

A.a1 -> B.b1
B.b1 -> A.a2

A.a1是一个MySQL事务操作,会将某个表中某个记录的某个字段值从 value1 修改 value2,然后调用 B.b1 ,B.b1 再通过 A.a2 查数据,因为事务还没提交,所以读到的数据是 value1。

但,B.b1 想要的是是 value2 ,那么这种实现就会有问题。

解决方案:

具体情况具体分析,比如分析能否去掉事务,能否让事务粒度变小,B能否异步延时调用A?



( 本文完 )