情景回顾
最近生产环境出现微服务线程卡死的情况,导致业务无法正常执行,通过JStack排查是由于动态刷新nacos后会导致线程阻塞,现在对于这种情况作简单演示。
nacos说明
Nacos是一个较常使用的服务配置和发现的框架,如果使用不当确实可能会出现死锁,导致线程阻塞。不过,由于版本迭代,具体的场景会有差异。一般来说,如果发生死锁,多是由于资源竞争和不按顺序获取多个资源导致。
以下是一个理论上可能出现死锁的场景(虚拟示例),在动态刷新配置(如使用Spring Cloud Alibaba Nacos Config)时可能出现:
@RefreshScope
@Service
public class SomeService {
@Value("${some.config}")
private String someConfig;
public void doSomething() {
synchronized(someConfig) {
// ... 执行一些操作
}
}
}
在如上代码中如果动态刷新了some.config
配置,而多个线程进入了doSomething()并且检查到someConfig的更改,可能造成它们之间的相互阻塞。这尤其在代码同步块较大或其他 synchronized 块嵌套使用时更常见。
这里应用程序通过@RefreshScope
注解用来实现配置自动更新。@RefreshScope
设置在一个Bean上,当检测到配置更改时,Spring会尝试重新初始化这个Bean。假设在刷新这个Bean的过程中,另外一个线程正在调用doSomething()
方法(因为它是同步的),这可能会导致死锁。
为了避免这种情况,在设计时应该确保:
- 外部配置和内部状态之间需要有清晰的分离。
- 在设计Bean的同步策略时要小心,避免在受Spring管理和需要动态刷新的Bean上使用
synchronized
。考虑使用其他同步机制。 - 审慎使用
@RefreshScope
以最小化使用其范围,并关注可能的Bean循环依赖。 - 如果遇到复杂的Bean依赖关系和多并发操作,建议增加适当的并发控制测试,以检测可能的死锁情况
- 最好不要在@Value注入的字段上使用synchronized关键字;对于需要排他控制的逻辑,采用其他并发工具类,如ReentrantLock或者利用java.util.concurrent下的其他并发类。
- Review代码,避免使用大的同步块,并适当拆分逻辑减小锁的粒度,尽量降低不同操作间的锁竞争