微服务死锁问题复盘
问题描述
在微服务A和B之间发生了死锁情况:
- 服务A发起同步RPC调用请求服务B的
GetUser
方法 - 服务B在处理
GetUser
请求时,需要调用服务A获取出战信息 - 此时服务A正在阻塞等待服务B的响应,无法处理新的请求
- 结果导致两个服务互相等待,形成死锁
问题根源
- 不当的循环依赖:服务A依赖服务B的数据,服务B又依赖服务A的数据
- 同步阻塞式调用:使用同步RPC加剧了问题,使系统更容易陷入死锁
- 方法复用不当:直接复用了
GetUser
这个”重量级”方法,而实际只需要部分数据
历史背景
- 最初服务B提供了
GetUser
方法,该方法会聚合多个服务的数据(包括公会信息、出战信息等) - 后来服务A需要服务B的部分数据,直接复用了
GetUser
方法 - 随着业务发展,
GetUser
方法内部逻辑扩展,开始需要服务A的数据
解决方案
- 拆分聚合方法:
- 将
GetUser
拆分为基本数据获取和扩展数据获取 - 为服务A创建专用的轻量级数据接口,只返回必要字段
- 将
- 避免循环依赖:
- 重新设计数据流,确保服务间调用是单向的
- 如果必须双向依赖,考虑通过消息队列异步处理
- 缓存策略:
- 对于不常变的数据,服务B可以缓存服务A的相关数据
- 设置合理的缓存过期时间
- 架构调整:
- 考虑将高频互相访问的数据合并到一个服务中
- 或者引入第三个服务作为数据聚合层
经验教训
- 谨慎复用方法:看似方便的复用可能隐藏着设计问题
- 警惕循环调用:微服务间调用应保持清晰的层级关系
- 接口设计原则:接口应该单一职责,避免”全能”方法
- 同步调用风险:同步阻塞式调用在复杂依赖关系中容易导致问题
改进计划
- 拆分
GetUser
方法,创建专用接口 - 评估将部分数据缓存在服务B的可行性
- 审查其他服务间调用是否存在类似风险
- 建立微服务调用规范,禁止循环同步调用