一. 配置方式
这里只说与Spring集成后的配置方式,这也是项目中主要使用的方式
Apollo
- 在属性上直接加@value注解,这个属性就会随着配置的更改动态更新
- 类实现ConfigChangeListener,在类中方法上@ApolloConfigChangeListener注解,注解在方法上,监控配置的变化,配置变化后会自定义方法来达到动态刷新bean的目的
Nacos
- Nacos无需任何配置,即可对有@ConfigurationProperties注解的类进行配置的动态刷新
- Nacos自2022.0.0.0-RC1版本后可通过spring.cloud.nacos.config.refresh-bahavior指定刷新模式,默认是all_beans(刷新所有bean),可选specific_bean (只刷新有配置值更改的bean)
优劣对比
先说结论,二者的优劣势对比,具体的原理机制等相对复杂,放后文详细讲解
Apollo
优势
- 提供出listener,使用者可灵活自行定制bean的刷新方式
劣势
- Apollo除@Value注解外不提供动态刷新的默认实现方案,而@Value注解平常用的较少,就比较鸡肋,用户想要用@ConfigurationProperties配置类的方式动态刷新,必须要自己去实现
Nacos
优势
- 有动态刷新的默认实现,用户可直接使用,且从2022.0.0.0-RC1版本后可自选bean的刷新模式
劣势
- 不管选用哪种nacos的刷新方案,RefreshScope域都会全刷新,触发RefreshScopeRefreshedEvent事件的发布,如eureka会订阅该事件,并于该事件发布时,触发eurekaClient的重新注册,若是配置热更新的比较频繁,那么会触发eurekaClient的频繁重注册
最终抉择
- 动态热更新的时候,肯定是更新哪个配置,那么只将与这个配置对应的bean进行更新最好,若是用的nacos,那么可选用2022.0.0.0-RC1版本,bean的刷新行为选用specific_bean指定刷新,若是用的Apollo,需自己实现,也可参考nacos中的SmartConfigurationPropertiesRebinder类,进行自实现,配置动态刷新后,建议还是要发布下RefreshScopeRefreshedEvent事件,使得依赖该事件发布的其他组件,在配置刷新后,可重新配置与该之相关的的内容,避免真的更改了例如eureka的配置后,eureka因不能重注册client导致的配置无法生效的问题。这里比较坑的地方就是springcloud没有提供一种机制,可监听自己的配置更改及事件发布对应着触发事件,进行导致只是更改了用户自定义的一些配置也触发了eureka客户端重注册这种看似风马牛不相及的行为出现
动态刷新机制
这里只以nacos为例,重点讲解服务通过监听器拿到配置变更之后的流程,Apollo在自行实现时,也可参考此流程
- NacosContextRefresher监听ApplicationReadyEvent事件,会在应用准备启动时间发布后,注册NacosListener
- 在注册时,会实现listener的innerReceive方法,在配置变更后,会通知到该方法,该方法会触发事件的发布:
applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// many Spring context
if (this.ready.compareAndSet(false, true)) {
this.registerNacosListenersForApplications();
}
}
/**
* register Nacos Listeners.
*/
private void registerNacosListenersForApplications() {
if (isRefreshEnabled()) {
for (NacosPropertySource propertySource : NacosPropertySourceRepository
.getAll()) {
if (!propertySource.isRefreshable()) {
continue;
}
String dataId = propertySource.getDataId();
registerNacosListener(propertySource.getGroup(), dataId);
}
}
}
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
// 这里发布RefreshEvent事件,用以刷新bean实例
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugE