采用 Nacos 和 Apollo 作为配置中心时@ConfigurationProperties、@RefreshScope的正确用法

心中剑-手中剑

    • 剑起
    • 剑来
    • 剑魂

剑起

众所周知,当前各个互联网企业采用的配置中心中,无外乎 Nacos 和 Apollo 最为知名。今天不去比较优劣,因为适合自己的才是最好的,而是在两个框架出现过渡时,有些用法或者编码习惯需要调整,否则配置文件就自己单飞了。

最近因为公司技术栈过渡原因,我们从 Nacos 转为 Apollo 作为配置中心,这期间原来为了从编码规范的习惯不少类采用ConfigurationProperties注解修饰,而在接入 Apollo 配置中心后,猛然发现@RefreshScope无法自动热更新,这无疑是致命的。


剑来

下面演示Nacos 迁移到 Apollo不生效的场景

@Component
@RefreshScope
@ConfigurationProperties(prefix = "elasticsearch.check")
public class EsCheckConfig {
   // 迁移Apollo 后如果不做额外的配置 手动修改值是无法被感知的
   private Integer flag;
}

分析: 采用 SpringCloud 原生注解@RfreshScope,迁移到 Apollo 后对自定义配置的控制权都完全丢失,所以丢失相应的功能也能理解;既然不是一个体系,也不必考虑太多。

正确的打开方式

  1. 完全之策
直接依赖 SpringBoot 原生的@Value 注解,而这注定有几个问题
1. 虽然同样解决了问题,好像大家都知道的完全之策,更像是废话
2. 可偏执的我们总想知道@ConfigurationProperties 这家伙能不能热更新。
3. 如果像我们在已经搭建的体系下,去使用@Value方式,必然耗费一番功夫,而且几乎需要走一遍所有用例,确保迁移正确。
  1. 他山之石-稳定版
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.stereotype.Component;

import java.util.Set;

@Slf4j
@Component
public class ApolloConfig {


    private final RefreshScope refreshScope;
    
    private final EsCheckConfig esCheckConfig;


    public ApolloConfig(RefreshScope refreshScope, EsCheckConfig esCheckConfig) {
        this.refreshScope = refreshScope;
        this.esCheckConfig = esCheckConfig;
    }


    @ApolloConfigChangeListener
    private void someOnChange(ConfigChangeEvent changeEvent) {
        // 这部分是为了解决ConfigurationProperties 无法获取 apollo 变动的日志
        Set<String> changedKeys = changeEvent.changedKeys();
        changedKeys.forEach(k -> {
            ConfigChange change = changeEvent.getChange(k);
            log.info(change.toString());
        });
        // apollo 自带的热刷新
        refreshScope.refresh("esCheckConfig");
//        refreshScope.refreshAll();
    }
}

这下虽然满足了诉求,而这又带了几个问题【以下是戏精环节】

  1. 这 TMD 太离谱了,我那么多ConfigurationProperties 修饰的类,都要注入一遍?好,Apollo 还算良心,看到上面的注释了嘛,有了 refreshAll()
    好像再也不需要注入所有的类,于是长呼一口气,等等 我草这玩意儿是不是没更新都会刷新一遍,Yes!但凡大中型企业的配置,没有上万也有上千。
  2. 还有个问题,这只能解决你当下服务的配置,那么多微服务如果不依赖一个公共的底层服务去处理,那也是掩耳盗铃。

哦,对了上面还有简陋版

区别在于refreshScope.refresh("esCheckConfig")底层也是使用了this.applicationContext.publishEvent 不同的是在 spring 的发布事件基础上加锁了,Apollo 开发者也是细腻和全面,不过有谁一天无聊的操作同一个配置呢?如果只有几个人维护的话用下面倒也未尝不可

public class ApolloConfig implements ApplicationContextAware {


    private ApplicationContext applicationContext;

    @ApolloConfigChangeListener
    private void someOnChange(ConfigChangeEvent changeEvent) {
        Set<String> changedKeys = changeEvent.changedKeys();
        changedKeys.forEach(k -> {
            ConfigChange change = changeEvent.getChange(k);
            log.info(change.toString());
        });
        // 更新@ConfigurationProperties注解的bean
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys));
    }
}

所以,实际开发中该怎么做?笔者探寻一圈,难以两全,所以有了下文


剑魂

使用@Value一定没毛病的老铁哈哈哈,所以在你编码习惯上就应该有这样的意识,如果还在使用@ConfigurationProperties的老铁还是尽早悬崖勒马,否则早晚要陷入我等这样的困境,实在是想用,就要实时记得用apollo 自带的热更新去检查

当然了,如果像Redis、MySQL 的这样固定的配置,@ConfigurationProperties也是没毛病,当你要修改这些基础配置时,重启在所难免,
遗憾的是 SpringBoot 都集成了

还有,细心的童鞋应该还发现上面一段记录ConfigChangeEvent打印变更key 的先后信息,这个在使用@ConfigurationProperties避免带来的隐患,该注解修饰下的对象被 spring代理。

因此当你发生值变更的时候,what 你啥也感知不到,好像啥也没发生,这样的事情你允许在生产环境吗?所以使用@value吧,直到 SpringBoot 也能自带支持@ConfigurationProperties热更新那一天!

你可能感兴趣的:(前车之鉴,java,SpringBoot,Apollo,Nacos,配置热更新)