@RefreshScope注意事项

1.前言

我们都知道@RefreshScope是SpringCloud用于动态刷新我们配置文件内的配置项数据,可以在不重启服务的情况下就能动态更新配置项。使用方式一般是将其修饰在类上,再用@Value注解修饰需要刷新的字段上,或运用@ConfigurationProperties注解,只要在 Bean 上添加上了这个注解,指定好配置文件的前缀,那么对应的配置文件数据就会自动填充到 Bean 中。通常,我们会认为@RefreshScope可以修饰在任何被Spring管理的Bean上,其实不然。

2.@RefreshScope修饰在业务类上的问题

可能有人会说修饰在业务类上不是很正常吗,很多网站给的demo也是这么给的啊。的确,这是在你业务方法执行比较快的前提下,如果业务方法执行较慢,此时我们在配置中心重新更新了配置项,此时就存在一定的阻塞问题。

下面这段信息是线上服务假死状态下打印的线程堆栈信息,不只是当前306号线程,其他线程也同样是被阻塞保持等待状态:

可以看到在执行controller调用service时,会去获取当前service的代理对象去执行,然而在执行前都阻塞在了一个读锁上。我们知道,读锁是共享锁,如果都是读锁的情况下是不会造成阻塞的,因此在获取读锁之前,肯定有其他线程获取了写锁。

对GenericScope类走读下代码发现写锁是在其destroy方法内获取的,查找其方法引用可以看到恰好有个RefreshScope类调用了该方法,可以大致确定配置项动态刷新时,会调用destroy方法执行对应的销毁逻辑。本地debug后也证实了该假设。

按理说destroy方法应该执行很快才对,又不涉及io相关操作,怎么会迟迟不释放写锁呢?

可以确定,那就是写锁前面还有其他读锁或写锁占有了该资源。因为我们刷新配置才会获取写锁,一般不太可能做到频繁的刷新,那可能就是读锁占用了现有资源。再来看下读锁的代码逻辑:

可以看到在获取了读锁后通过反射调用被代理对象方法,执行完才会释放锁。如果修饰在业务类上,当调用该业务类中某个方法比较耗时时,且此时又动态更新了配置项,在它后面插入了一条写锁,此时后续要获取该类执行方法调用要拿的读锁就全都被阻塞了,需要等待那个比较耗时的方法执行完后,destroy方法拿到写锁执行销毁逻辑释放写锁后,后续的业务方法才能继续执行。

如果我们只把配置项单独拎出来,通过反射调用的方法就是一个简单的get操作拿配置项的值,基本没有什么耗时。

3.总结

在使用@RefreshScope时,不建议把它修饰在业务类中,应保持该类的职责单一,统一聚合在一个类中,只是获取配置,不参与任何业务逻辑。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值