1 Nacos动态刷新

不需要开发,基于文件刷新机制就可以满足

  • 增加Nacos的POM依赖

    1. <dependency>
    2. <groupId>com.alibaba.cloud</groupId>
    3. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    4. </dependency>
  • bootstrap.properties配置Nacos Config

    spring.application.name=gateway-inner
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    spring.cloud.nacos.config.group=DEFAULT_GROUP
    
  • Nacos 控制台添加项目属性文件配置

    2 原理

    2.1 流程图

    Nacos动态刷新.png

    2.2 源码分析

    2.2.1 文件更新监听

  • com.alibaba.nacos.client.config.impl.ClientWorker

  • com.alibaba.nacos.client.config.impl.CacheData

    // Nacos客户端长连接
    public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
              final Properties properties) {
          this.agent = agent;
          this.configFilterChainManager = configFilterChainManager;
    
          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;
              }
          });
          // 10毫秒检测配置更新
          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);
      }
    
    class LongPollingRunnable implements Runnable {
          private final int taskId;
          public LongPollingRunnable(int taskId) {
              this.taskId = taskId;
          }
          @Override
          public void run() {
    
              List<CacheData> cacheDatas = new ArrayList<CacheData>();
              List<String> inInitializingCacheList = new ArrayList<String>();
              try { 
                  // 1:检查服务配置
                  List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                  if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                      LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                  }
                  for (String groupKey : changedGroupKeys) {
                      try {
                          // 2:获取服务配置
                          ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);
                      }
                  }
                  for (CacheData cacheData : cacheDatas) {
                      if (!cacheData.isInitializing() || inInitializingCacheList
                              .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
                          // 3:检查监听
                          cacheData.checkListenerMd5();
                          cacheData.setInitializing(false);
                      }
                  }
              } 
          }
      }
    

    2.2.2 发布RefreshEvent事件

  • com.alibaba.cloud.nacos.refresh.NacosContextRefresher

    ·    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);
                          applicationContext.publishEvent(
                                  new RefreshEvent(this, null, "Refresh Nacos config"));
                      }
                  });
          try {
              configService.addListener(dataKey, groupKey, listener);
          }
      }
    

    2.2.3 处理RefreshEvent事件发布RefreshScopeRefreshedEvent事件

  • org.springframework.cloud.endpoint.event.RefreshEventListener

      @Override
      public void onApplicationEvent(ApplicationEvent event) {
          if (event instanceof ApplicationReadyEvent) {
              handle((ApplicationReadyEvent) event);
          }
          else if (event instanceof RefreshEvent) {
              handle((RefreshEvent) event);
          }
      }
      public void handle(RefreshEvent event) {
          if (this.ready.get()) { // don't handle events before app is ready
              Set<String> keys = this.refresh.refresh();
          }
      }
    
  • org.springframework.cloud.context.refresh.ContextRefresher

      public synchronized Set<String> refresh() {
          Set<String> keys = refreshEnvironment();
          this.scope.refreshAll();
          return keys;
      }
      public void refreshAll() {
          super.destroy();
          this.context.publishEvent(new RefreshScopeRefreshedEvent());
      }
    

    2.2.4 处理RefreshScopeRefreshedEvent发布RefreshRoutesEvent

  • org.springframework.cloud.gateway.route.RouteRefreshListener

      @Override
      public void onApplicationEvent(ApplicationEvent event) {
          if (event instanceof ContextRefreshedEvent) {
              ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
              if (!WebServerApplicationContext.hasServerNamespace(
                      refreshedEvent.getApplicationContext(), "management")) {
                  reset();
              }
          }
          else if (event instanceof RefreshScopeRefreshedEvent
                  || event instanceof InstanceRegisteredEvent) {
              reset();
          }
          else if (event instanceof ParentHeartbeatEvent) {
              ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
              resetIfNeeded(e.getValue());
          }
          else if (event instanceof HeartbeatEvent) {
              HeartbeatEvent e = (HeartbeatEvent) event;
              resetIfNeeded(e.getValue());
          }
      }
      private void reset() {
          this.publisher.publishEvent(new RefreshRoutesEvent(this));
      }
    

    2.2.5 处理RefreshRoutesEvent

  • org.springframework.cloud.gateway.route.CachingRouteDefinitionLocator

  • org.springframework.cloud.gateway.route.CachingRouteLocator