避免RPC回调死锁
(金庆的专栏 2020.9)
今天发现一例 RPC 回调死锁,现象为一串相关的 RPC 调用全部超时失败。
A 调用 B 的 RPC, B 再回调 A, 形成一个 RPC 调用环后,
如果 A 调用 B 时先加了一个锁,然后 B 回调 A 时又需要这个锁,
而此时 A 正在等待 B 的 RPC 返回,之后才会释放锁,
这样就形成了死锁。这个 RPC 调用环最终会全部超时失败。
这个调用环可能会涉及多个服务,如 A->B->C->...->A。
避免回调死锁有以下方法。
## 避免回调,不要有 RPC 调用环
一般的服务依赖应该都是无环的。
可以画一个服务依赖图,如果没有形成调用环,就可以放心不会有死锁。
## RPC 调用时不要加锁
一般进入某个 RPC 处理时,会锁住相关的资源,直到处理完成。
如果处理过程中需要向外发出 RPC 请求,应该先释放锁,待请求完成后再次获取锁。
如果请求过程中需要禁止其他协程操作相关资源,也可以不释放锁,
但锁的使用上应该允许加锁失败,不要等待锁。
锁应该是能够快速释放的。如果需要加锁去执行一个长时间的操作,
这个锁的设计可能需要重新考虑。
## 打断调用链
有时候 RPC 调用链不必是阻塞等待的。
如通知性的,无返回值的RPC, 不需要等待他返回,可以开一个新的协程去执行 RPC.
父RPC等待子RPC返回,改为父RPC直接返回,子RPC后台执行,即断开 RPC 的调用依赖。
RPC 调用链断开成多段后,调用循环的可能性就大大下降了。