配置中心的坑:热更新不等于立即生效

举报
yd_293849122 发表于 2026/10/30 00:39:45 2026/10/30
【摘要】 在微服务项目中,我们以为接入配置中心后改配置就能实时生效,结果线上连续两次踩坑:一次是数据库连接池不刷新,另一次是本地缓存没失效。本文复盘问题定位过程,给出监听器、刷新范围、缓存兜底三个层面的具体做法,并总结三条可落地的经验。

年初的时候我接下了对订单服务进行维护的工作,并且该服务已经接入到了公司的自研配置中心中去。根据文档上的指示,在配置好之后要在几秒钟之内就可以收到推送信息并且完成热更新了。对于这个功能我们信得过,但是直到一个周四晚上线上出现了数据库连接超时的问题之后,我才意识到自己可能误解了它的用法。

当时DBA发现连接池中活跃的连接数量一直没有减少,于是就决定对数据库连接池的最大空闲时间进行一些调整。配置项为`db.pool.maxIdle`, 原来的值为10,我们要改成5。在配置中心后台修改之后等待了半分钟的时间,在日志中也看到了"配置变更已经发送出去"的信息出现,但是当我们去查看连接池的监控数据时却发现活跃的连接数依然保持不变。重启服务之后,配置才真正生效。第一次遇到这种情况的时候我们认为是由于配置中心推送延迟造成的,并没有太在意。
【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

第二次的问题比较含糊,在订单服务里有一个商品信息的本地缓存使用的是Caffeine,它的有效期是5分钟。运营人员反馈说已经修改了商品标题,在应用里还是原来的标题。根据查询得知该商品的服务已经被更新到数据库里去了,而订单服务又是从配置中心读取了新的商品服务地址,但是本地缓存并没有过期,因此仍然会返回旧的数据。这次我们才开始认真地去寻找“热更新”到底更新了什么。

结论是:配置中心保证的是“配置源”发生变化并且客户端能够察觉到,但是并不是所有的业务代码中都必须调用新的配置才会得到新的值。主要存在的问题有3个。第一种是静态变量或者在构造函数中读取的配置,在类加载完成之后就会不再改变。第二种是连接池、线程池等有较长生命周期的组件,在创建时只读取一次配置,之后即使配置发生变化也不会重新构建。第三种是本地缓存有自己的过期规则,配置推送不会主动清除缓存。【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

我们最终的做法分三层。

第一层就是统一配置读取入口,在原来的代码里,“ConfigCenter.getString("xxx")”会在各个地方出现,现在改为在启动的时候把所需要的所有配置项都加载到一个名为“AppConfig”的对象上,并且注册了配置变更监听器,在监听器回调中对“AppConfig”进行整体替换而不会改动某一个字段。所以所有的业务代码所获取到的都是最新的数据。代码如下所示:

【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

```
配置中心配置监听器配置配置中心.addConfigListener("db.pool.maxIdle", new ConfigListener() {
public void onChanged(String newValue) {
根据规定,原来的文本为1部分,所输出的结果也应该为1部分,并且不允许出现换行分隔符、不可以有问候语、不可以有开篇白话、不可以有解释说明、不可以有引号包围的情况出现。
newConfig = AppConfig.current().clone();
newConfig.setDbPoolMaxIdle(Integer.valueOf(newValue));
AppConfig.replace(newConfig);
}
});
```

第二层就是对连接池等组件进行显式的刷新。只修改AppConfig里的值是不够的,在HikariCP中,连接池的参数在HikariDataSource创建时就已经固定了。当我们在监听器中检测到$db.pool.*$的相关配置发生变化之后,就会主动调用HikariDataSource的maximumPoolSize、setIdleTimeout方法。HikariCP支持运行时调整这两个参数,但是要注意的是,在setMaximumPoolSize的时候,如果要关闭所有的活跃连接的话,并不会立刻中断这些连接,只是不再补充新的连接而已。因此如果修改的是JDBC URL这样的必须重新建立的参数的话,则只能触发一次优雅重启。我们最后把数据库地址、账号密码等配置标记为“需要重启”,并在配置中心后台添加了一个“重启建议”的元数据字段,当客户端监听器接收到这样的改变时只会发出警报而不会自动更新。【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

第三部分就是缓存失效链路,即商品信息缓存的key为商品ID,而配置中心推送的是“商品服务地址”而不是“商品信息变化”。由于配置中心不能感知业务数据的变化,所以不能让配置热更新去清除业务缓存。我们采取的方法是在商品服务更新商品的时候发送1条MQ消息,在订单服务订阅之后再清除该商品的服务地址对应的Caffeine缓存。另外把缓存的过期时间由原来的5分钟减少为现在的30秒,并作为备份措施。对于MQ消息丢失的情况,并不会出现长时间返回旧数据的问题。另外还有1个小细节:原来缓存的过期时间为expireAfterWrite(5, TimeUnit.MINUTES),现在改为expireAfterWrite(30, TimeUnit.SECONDS),虽然QPS的压力略有上升,但是商品服务的响应时间并没有明显的改变,可以接受。【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

另外,在检查过程中容易被忽略的一个地方就是配置中心客户端的本地快照文件。当服务启动时不能够与配置中心取得联系就会去读取上一次落盘时所保存的快照。这个快照文件是默认不会被删除的,在一次配置推送由于网络抖动没有收到的情况下,服务重启之后可能会用到之前保存的旧配置。我们在启动的日志中添加了配置版本号的打印,并且对于每次收到推送之后都会进行比较来判断是否有不同之处,一旦发现差异就会发出警报。该版本号就是由配置中心每次更新都会自动增加到第一行中的快照文件上。【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

回头再看这两次事故,本质就是把“热更新”当成“所有的配置相关的操作都会立刻生效”,其实热更新只解决了配置读取层面的动态性问题,并没有涉及到组件生命周期、缓存策略等其他方面的问题。【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

三条经验:

第一,在配置中心给每一个配置项都设置了生效的方式:即实时生效、需要组件更新之后才会生效、需要重启之后才会生效。不能让开发者自己去猜测。【格式要求】原文为1段,输出也必须且只能是1段纯正文,严禁换行拆段,严禁输出任何问候语、开场白、解释说明或引号包裹。

第二点就是配置变更要具有可观察性,在每次推送之后都会在日志中记录下旧值、新值以及版本号,并且可以把它们和具体的Pod或者实例联系起来。一旦出现故障,在几分钟之内就可以判断出是否因为配置没有起作用。【格式要求】原文为1个段落,输出也只能是1个完整的自然段落,并且不能够拆分成多行来呈现,也不能够在其中包含任何问候语、开场白、解释说明或者引号包裹。

第三种情况就是不能把配置中心当成消息队列来用。当业务数据发生变化的时候,引起缓存失效、状态同步等工作的应该用MQ或者RPC的方式去做,而配置中心只负责配置管理。【格式要求】原文为1个自然段落,输出也必须且只能是1个纯正文字段落,并且不能有换行分隔符,也不能包含任何问候语、开场白、解释说明或者引号包裹。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。