irpas技术客

【Nacos】生产上需要不重启服务的情况下刷新配置,这个方法可以试试_legendaryhaha_nacos修改配置后需要重启服务吗

irpas 5448

【Nacos】生产上需要不重启服务的情况下刷新配置,这个方法可以试试 配置Demo搭建RefreshScope 原理


假设Nacos服务已经搭建完成

配置 新建service-config配置文件,9d0c9401-f575-4427-a982-eb8637a652b0为区别命名空间的ID,自动生成。 Demo搭建 引入依赖 <!-- nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.1.3.RELEASE</version> </dependency> resources下新建 bootstrap.properties 配置文件,配置信息如下: spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.file-extension=yml spring.application.name=service-config spring.cloud.nacos.config.namespace=9d0c9401-f575-4427-a982-eb8637a652b0 # 项目启动nacos配置信息打印级别,info会打印出配置信息 logging.level.com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder=warn 关键注解RefreshScope @RestController @RefreshScope public class TestNacosController { @Value("${top.fsn.id}") private String id; @RequestMapping("test") public String test() { return id; } } RefreshScope 原理

该注解支持动态更新配置文件中数据,无需重启服务。另外,该注解及实现方式也是SpringCloud下,通过拓展Spring下的Scope(作用域)接口进行实现。其注解代码如下:

注解的代码很简单,可以看到它返回的是ScopedProxyMode(就姑且叫代理模型吧)对象。关于代理模型有如下四种类型(实际就三种)。 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") @Documented public @interface RefreshScope { /** * @see Scope#proxyMode() * @return proxy mode */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; } public enum ScopedProxyMode { /** * Default typically equals {@link #NO}, unless a different default * has been configured at the component-scan instruction level. */ // 与NO类型一样,表示不使用代理 DEFAULT, /** * Do not create a scoped proxy. * <p>This proxy-mode is not typically useful when used with a * non-singleton scoped instance, which should favor the use of the * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it * is to be used as a dependency. */ NO, /** * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by * the class of the target object. */ // 使用 jdk dynamic proxy(基于接口的代理) INTERFACES, /** * Create a class-based proxy (uses CGLIB). */ // 使用 CGLIB 做代理(基于目标类) TARGET_CLASS; } RefreshScope 的实现,几个类结构如下, 再看看RefreshScope 类继承了GenericScope public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered 通过Debug可以知道启动时,调用onApplicationEvent 方法。在start方法有进行eagerlyInitialize初始化。 初始化的过程会获取一个name为scopedTarget.testNacosController

这个方法有一处不明白,讲道理,这个void方法,如果是我写,判断bean不为null,我直接return了,还getClass干嘛。。但是最近看过why大佬的一篇关于return null文章,顿时茅塞顿开。

if (bean != null) {bean.getClass();}

在getBean的时候有绕了一大圈:首先去到BeanFactory#getBean -> AbstractBeanFactory#getBean -> AbstractBeanFactory#doGetBean 在AbstractBeanFactory#doGetBean中,又通过如下方法,调用GenericScope #get方法,兜兜转转,又回到RefreshScope 的父类来了: # AbstractBeanFactory#doGetBean Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); # GenericScope #get @Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } 可以看到我们这里用 @RefreshScope注解的bean最终被放到一个locks中,它的定义如下: private ConcurrentMap<String, ReadWriteLock> locks = new ConcurrentHashMap<>(); 每个bean在初始后之后,还需要为其添加一个监听器,在继续debug之后,就来到了:

NacosContextRefresher#registerNacosListenersForApplications

添加监听器时,又会触发Nacos的客户端ClientWorker#addListeners方法,然后又会把相关的配置信息同步到一个cacheMap中,后面监听配置是否有变化时,就从cacheMap中获取数据:

其中 tenant=9d0c9401-f575-4427-a982-eb8637a652b0,即命名空间随出来的字符串标识

public void addListeners(String dataId, String group, List<? extends Listener> listeners) { group = null2defaultGroup(group); CacheData cache = addCacheDataIfAbsent(dataId, group); for (Listener listener : listeners) { cache.addListener(listener); } } ```cpp public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException { // ... synchronized (cacheMap) { CacheData cacheFromMap = getCache(dataId, group, tenant); if (null != cacheFromMap) { cache = cacheFromMap; // reset so that server not hang this check cache.setInitializing(true); } else { cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant); // fix issue # 1317 if (enableRemoteSyncConfig) { String[] ct = getServerConfig(dataId, group, tenant, 3000L); cache.setContent(ct[0]); } } Map<String, CacheData> copy = new HashMap<String, CacheData>(this.cacheMap.get()); copy.put(key, cache); cacheMap.set(copy); } LOGGER.info("[{}] [subscribe] {}", agent.getName(), key); MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.get().size()); return cache; } 在监听到服务端配置时,则得益于ClientWork#LongPollingRunnable内部类。 class LongPollingRunnable implements Runnable { // ... @Override public void run() { // ... try { // check failover config // ... // check server config // ... } } 在ClientWorker初始化后,会初始化executor 和 executorService ,executor 每隔 10毫秒(??有点奇怪 这么快的频率吗)启动 executorService 中的任务线程检查配置信息。

话说这种设计方式有点绕。。。

public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) { this.agent = agent; this.configFilterChainManager = configFilterChainManager; // Initialize the timeout parameter init(properties); this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("com.alibaba.nacos.client.Worker." + agent.getName()); t.setDaemon(true); return t; } }); this.executorService = Executors .newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName()); t.setDaemon(true); return t; } }); this.executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { try { checkConfigInfo(); } catch (Throwable e) { LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e); } } }, 1L, 10L, TimeUnit.MILLISECONDS); }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Demo搭建引入依赖amplt #nacos #ampgt