首页
学习
活动
专区
圈层
工具
发布

SpringBoot + @RefreshScope:动态刷新配置的终极指南

前两天晚上在公司楼下抽烟的时候,我们组的小李跟我吐槽,说他们项目的配置改一次得重启服务,搞得他半夜在群里发“重启完成”截图,特别痛苦。我一听就笑了,这不就是没用 Spring Cloud 的动态刷新嘛。然后我跟他说,其实 SpringBoot 里用@RefreshScope就能很优雅地解决这个问题,不需要写一堆乱七八糟的逻辑。

其实这种问题我也踩过坑,尤其是早几年微服务刚火的时候,很多人一股脑把配置都写到application.yml里面,结果一旦要改数据库密码、限流阈值之类的配置,就只能重启服务。生产环境里重启一次有时候要排窗口期,甚至得熬到半夜,真是折腾人。后来接触了@RefreshScope,算是找到了一个还算优雅的解法。

配置为什么需要动态刷新

先别急着看代码,我们先想个生活里的例子。就好比你家空调的温度,如果每次想调节温度都得拔电源再插上,你肯定受不了。正常情况是不是按遥控器一按,温度立马就变?动态刷新配置其实就是这么个事,让服务不用重启就能感知到最新配置。

那到底什么场景需要?举几个常见的:

数据库连接的用户名密码变了,得马上切换

线上某个接口要临时限流,把阈值调低点

日志级别想改一下,从INFO提到DEBUG

如果每次都靠重启服务,那运维会骂死你。所以啊,配置热更新几乎是微服务项目的标配。

@RefreshScope 到底是干嘛的

说人话:@RefreshScope这个注解就是帮你把 Bean 放到一个“可刷新”的作用域里。你平常的 Spring Bean 默认是单例的,一旦创建就固定住了。而加了@RefreshScope之后,Spring 会在你触发 refresh 事件的时候,销毁老 Bean,重新创建一个新的,把最新的配置值注入进去。

举个最简单的例子,你的配置里有这么一段:

myapp:

greeting: "Hello World"

然后你在代码里写:

@Component

@RefreshScope

public class GreetingService {

  @Value("${myapp.greeting}")

  private String greeting;

  public String sayHello() {

      return greeting;

  }

}

正常情况下,这个greeting在启动时会被注入。如果你把配置文件里的"Hello World"改成"你好,世界",默认情况下是不会变的。但加了@RefreshScope之后,你只要触发刷新,这个值就会更新。

怎么触发刷新?

这里很多人第一次玩都会卡住。其实触发刷新有几种方式:

手动调用 Actuator 提供的/actuator/refresh接口前提是你引入了spring-boot-starter-actuator,然后配置里允许refresh这个端点。调用这个接口就能刷新。

management:

endpoints:

  web:

    exposure:

      include: refresh

然后用 curl 请求:

curl -X POST http://localhost:8080/actuator/refresh

这个时候,你的 Bean 就会被销毁重建,新的配置值生效。

结合 Spring Cloud Config / Nacos如果你用的是配置中心,比如 Spring Cloud Config、Nacos、Apollo,这些都会帮你自动监听配置变化,然后触发 refresh。 这样就不用手动调接口了,完全自动化。

和配置中心搭配的“骚操作”

我以前在项目里干过一件挺有意思的事:我们系统有一堆限流参数,一开始是写死在配置文件里的,改动一次要重启,很麻烦。后来接了 Nacos 配置中心,然后用@RefreshScope来刷新。效果就是运维只要在 Nacos 控制台改个值,几秒钟之内服务就能感知到,接口限流立马生效。

比如限流的配置:

rate:

limit: 100

代码里写:

@Component

@RefreshScope

public class RateLimitConfig {

  @Value("${rate.limit}")

  private int limit;

  public int getLimit() {

      return limit;

  }

}

然后拦截器里直接用这个值判断就行。那次上线之后,领导还夸了句“这个很灵活”,我心想这不就是@RefreshScope的功劳嘛。

@ConfigurationProperties 和 @RefreshScope 一起用

这里有个坑我得提醒一下。很多人喜欢用@ConfigurationProperties来绑定配置,比如:

@Component

@ConfigurationProperties(prefix = "rate")

@RefreshScope

public class RateLimitProperties {

  private int limit;

  // getter setter

}

这样写是完全没问题的。Spring 会在 refresh 的时候重新绑定配置。但是如果你忘了加@RefreshScope,那就呵呵了,刷新不会生效。

我在这几年里踩过一些坑,给你们总结下:

Bean 不在容器里?那刷新个啥有人把配置值直接用@Value注在某个方法参数里,结果刷新没效果。原因就是@RefreshScope必须加在 Bean 上,Spring 才能感知到。

静态变量刷新不了千万别想着@Value注入到static变量里,这玩意不会更新。因为 Spring 根本没办法替你重新赋值。要想更新,老老实实放到 Bean 里。

性能问题每次刷新其实是销毁重建 Bean,如果这个 Bean 初始化特别重(比如要连数据库、初始化缓存),那刷新时会有点卡顿。解决办法是把这些大对象单独拆出去,不要全绑在@RefreshScopeBean 里。

来个完整的 Demo

给你们看一个简单的 SpringBoot Demo,假设我们要动态刷新欢迎语。

application.yml:

myapp:

welcome: "欢迎使用系统"

WelcomeConfig.java:

@Component

@RefreshScope

public class WelcomeConfig {

  @Value("${myapp.welcome}")

  private String welcome;

  public String getWelcome() {

      return welcome;

  }

}

WelcomeController.java:

@RestController

public class WelcomeController {

  @Autowired

  private WelcomeConfig welcomeConfig;

  @GetMapping("/welcome")

  public String welcome() {

      return welcomeConfig.getWelcome();

  }

}

然后你启动服务,访问/welcome,返回的是 “欢迎使用系统”。这时候你去配置中心或者本地改成 “系统升级啦”,刷新一下配置,再访问接口,马上就变了。

我印象最深的一次,是我们一个支付服务,数据库密码被 DBA 换掉了(安全要求),如果不用动态刷新,那天晚上全体服务得重启,影响一堆交易。幸好当时我们用的是 Spring Cloud + Nacos 配置,配合@RefreshScope,DB 的 DataSource 参数刷新了一下,连个重启都不用,业务照常跑。那一刻我是真的觉得这东西救命。

当然,这玩意也不是银弹。有时候配置变更的范围特别大,比如你换了 Redis 集群地址,这种刷新时会把连接池销毁重建,可能会有短暂的不可用。所以在生产用的时候还是要小心评估,不是所有配置都适合热更新。

总结一下

啰啰嗦嗦说了这么多,其实重点就三句话:

@RefreshScope让 Bean 支持配置热更新

刷新的触发方式要么是 Actuator,要么是配置中心自动触发

用的时候要注意坑,比如静态变量、Bean 初始化开销

如果你们的系统还在靠重启来更新配置,真的该试试这个东西,省心太多了。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OqfQb-bdor847OgVnTM4_Qhg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券