最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

源碼分析Nacos如何動態(tài)刷新配置

 更新時間:2025年08月07日 09:53:13   作者:林林code  
從架構圖上可以知道,Nacos提供了兩種服務,一種是用于服務注冊、發(fā)現(xiàn)的Naming?Service,一種是用于配置中心、動態(tài)配置的Config?Service,本文將從源碼角度分析Nacos如何動態(tài)刷新配置,需要的可以了解下

一 Nacos 刷新配置的源碼閱讀

在 ClientWorker 中配置了 定義了一個 的內部類 LongPollingRunnable 并實現(xiàn)了Runnable 接口 直接到 cacheData.checkListenerMd5() 這個方法

public void run() {
             // 獲取定義的Group
            List<CacheData> cacheDatas = new ArrayList<CacheData>();
            List<String> inInitializingCacheList = new ArrayList<String>();
            try {
                // check failover config
                for (CacheData cacheData : cacheMap.values()) {
                    if (cacheData.getTaskId() == taskId) {
                        cacheDatas.add(cacheData);
                        try {
                            checkLocalConfig(cacheData);
                            if (cacheData.isUseLocalConfigInfo()) {
                                cacheData.checkListenerMd5();
                            }
                        } catch (Exception e) {
                            LOGGER.error("get local config info error", e);
                        }
                    }
                }
            
                // check server config
                List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                    LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                }
            
                for (String groupKey : changedGroupKeys) {
                    String[] key = GroupKey.parseKey(groupKey);
                    String dataId = key[0];
                    String group = key[1];
                    String tenant = null;
                    if (key.length == 3) {
                        tenant = key[2];
                    }
                    try {
                        String[] ct = getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
                        cache.setContent(ct[0]);
                        if (null != ct[1]) {
                            cache.setType(ct[1]);
                        }
                        LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
                                agent.getName(), dataId, group, tenant, cache.getMd5(),
                                ContentUtils.truncateContent(ct[0]), ct[1]);
                    } catch (NacosException ioe) {
                        String message = String
                                .format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
                                        agent.getName(), dataId, group, tenant);
                        LOGGER.error(message, ioe);
                    }
                }
                for (CacheData cacheData : cacheDatas) {
                    if (!cacheData.isInitializing() || inInitializingCacheList
                            .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
                       // 檢查當前 配置文件的md5值是否改變
                        cacheData.checkListenerMd5();
                        cacheData.setInitializing(false);
                    }
                }
                inInitializingCacheList.clear();
            
                executorService.execute(this);
            
            } catch (Throwable e) {
            
                // If the rotation training task is abnormal, the next execution time of the task will be punished
                LOGGER.error("longPolling error : ", e);
                executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
            }
        }
    }

檢查當前的md5值是否更改

void checkListenerMd5() {
        for (ManagerListenerWrap wrap : listeners) {
            if (!md5.equals(wrap.lastCallMd5)) {
              // 如果md5值變了,就發(fā)送對應事件通知
                safeNotifyListener(dataId, group, content, type, md5, wrap);
            }
        }
    }

安全的通知監(jiān)聽器配置改變:

private void safeNotifyListener(final String dataId, final String group, final String content, final String type,
                                final String md5, final ManagerListenerWrap listenerWrap) {
    // 從包裝類中取出監(jiān)聽器
    final Listener listener = listenerWrap.listener;

    // 創(chuàng)建一個通知任務(異步或同步執(zhí)行)
    Runnable job = new Runnable() {
        @Override
        public void run() {
            // 當前線程的原始類加載器
            ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
            // 獲取監(jiān)聽器所屬類的類加載器(用于類加載隔離)
            ClassLoader appClassLoader = listener.getClass().getClassLoader();

            try {
                // 如果監(jiān)聽器是共享監(jiān)聽器的子類,設置上下文信息
                if (listener instanceof AbstractSharedListener) {
                    AbstractSharedListener adapter = (AbstractSharedListener) listener;
                    adapter.fillContext(dataId, group);
                    LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);
                }

                // 設置線程上下文類加載器為應用加載器(避免多應用部署時,SPI等加載錯類)
                Thread.currentThread().setContextClassLoader(appClassLoader);

                // 構造配置響應對象
                ConfigResponse cr = new ConfigResponse();
                cr.setDataId(dataId);
                cr.setGroup(group);
                cr.setContent(content);

                // 通過過濾鏈處理配置(比如解密、轉換等)
                configFilterChainManager.doFilter(null, cr);

                // 獲取處理后的配置內容
                String contentTmp = cr.getContent();

                // 調用監(jiān)聽器的 receiveConfigInfo 方法通知變更
                listener.receiveConfigInfo(contentTmp);

                // 如果是支持配置變更事件的監(jiān)聽器,觸發(fā)對應事件
                if (listener instanceof AbstractConfigChangeListener) {
                    // 解析變更內容(對比老配置和新配置)
                    Map data = ConfigChangeHandler.getInstance()
                            .parseChangeData(listenerWrap.lastContent, content, type);
                
                    // 構造事件對象并通知監(jiān)聽器
                    ConfigChangeEvent event = new ConfigChangeEvent(data);
                    ((AbstractConfigChangeListener) listener).receiveConfigChange(event);

                    // 記錄這次通知的內容
                    listenerWrap.lastContent = content;
                }

                // 更新上一次調用的 MD5 值
                listenerWrap.lastCallMd5 = md5;

                // 打印通知成功日志
                LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,
                        listener);
            } catch (NacosException ex) {
                // 特定 Nacos 異常處理
                LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}",
                        name, dataId, group, md5, listener, ex.getErrCode(), ex.getErrMsg());
            } catch (Throwable t) {
                // 捕獲所有其他異常,避免通知失敗影響主線程
                LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId,
                        group, md5, listener, t.getCause());
            } finally {
                // 恢復原始線程類加載器,避免線程池復用帶來問題
                Thread.currentThread().setContextClassLoader(myClassLoader);
            }
        }
    };

    // 記錄通知開始時間
    final long startNotify = System.currentTimeMillis();
    try {
        // 如果監(jiān)聽器提供了自定義線程池,則用線程池異步執(zhí)行
        if (null != listener.getExecutor()) {
            listener.getExecutor().execute(job);
        } else {
            // 否則直接當前線程執(zhí)行
            job.run();
        }
    } catch (Throwable t) {
        // 執(zhí)行過程出錯日志打印
        LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", name, dataId,
                group, md5, listener, t.getCause());
    }

    // 記錄通知完成時間
    final long finishNotify = System.currentTimeMillis();
    LOGGER.info("[{}] [notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",
            name, (finishNotify - startNotify), dataId, group, md5, listener);
}

NacosContextRefresher 中 registerNacosListenersForApplications的方法

/**
 * 為指定的 dataId + group 注冊一個 Nacos 配置監(jiān)聽器
 * @param groupKey 配置分組(group)
 * @param dataKey 配置標識(dataId)
 */
private void registerNacosListener(final String groupKey, final String dataKey) {
    // 生成一個唯一 key,用于標識監(jiān)聽器(key = group + "++" + dataId)
    String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);

    // 從 listenerMap 中獲取對應 key 的監(jiān)聽器,如果不存在則創(chuàng)建一個 AbstractSharedListener
    Listener listener = listenerMap.computeIfAbsent(key,
        lst -> new AbstractSharedListener() {
            /**
             * 當配置變更時,會觸發(fā)該方法
             */
            @Override
            public void innerReceive(String dataId, String group, String configInfo) {
                // 刷新次數(shù) +1(用于監(jiān)控/統(tǒng)計)
                refreshCountIncrement();

                // 記錄刷新歷史
                nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);

                // 發(fā)布 Spring 的 RefreshEvent,通知上下文環(huán)境配置已變更
                // 注意:這里是全量刷新,
                applicationContext.publishEvent(
                    new RefreshEvent(this, null, "Refresh Nacos config"));

                // 如果開啟了 debug 日志,打印變更信息
                if (log.isDebugEnabled()) {
                    log.debug(String.format(
                        "Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
                        group, dataId, configInfo));
                }
            }
        });

    try {
        // 調用 Nacos 客戶端 API,注冊監(jiān)聽器
        configService.addListener(dataKey, groupKey, listener);
    }
    catch (NacosException e) {
        // 注冊失敗,記錄警告日志
        log.warn(String.format(
            "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
            groupKey), e);
    }
}

在SpringClould 中的 RefreshEventListener

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
		log.debug("Event received " + event.getEventDesc());
		Set<String> keys = this.refresh.refresh();
		log.info("Refresh keys changed: " + keys);
	}
}

ContextRefresher 中的 refresh 方法刷新所有作用域為 refresh 的bean

public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
        // 刷新所有的
		this.scope.refreshAll();
		return keys;
	}

二 @refreshScope注解

定義

@RefreshScope 是 Spring Cloud 提供的注解,主要用于 ?支持配置的動態(tài)刷新?,特別是在結合像 Nacos、Consul、Spring Cloud Config 等配置中心時使用。@RefreshScope 使得標注的 Bean 在配置變更并發(fā)布刷新事件時,能夠被重新實例化,從而實現(xiàn)“?配置熱更新?”。

使用背景

Spring Boot 默認的 Bean 是單例的(@Singleton),一旦初始化完成,其屬性就不會再變化。如果你想在運行時通過配置中心動態(tài)刷新某個 Bean 中的屬性,就必須加上 @RefreshScope

與Nacos配合使用demo

1、依賴引入

確保你引入了以下依賴(以 Spring Boot 2.x / Spring Cloud Alibaba 為例):

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、application.yml配置

server:
  port: 8080

spring:
  application:
    name: nacos-refresh-demo
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        group: DEFAULT_GROUP
        namespace: public
        refresh-enabled: true

3、編寫配置類(使用 @RefreshScope)

package com.example.nacosdemo.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@Data
@Component
@RefreshScope // 開啟動態(tài)刷新
@ConfigurationProperties(prefix = "custom")
public class CustomConfig {
    private String name;
    private Integer age;
}

4、測試 Controller

package com.example.nacosdemo.controller;

import com.example.nacosdemo.config.CustomConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class TestController {

    private final CustomConfig customConfig;

    @GetMapping("/config")
    public String getConfig() {
        return "name: " + customConfig.getName() + ", age: " + customConfig.getAge();
    }
}

然后更改你的Nacos中的配置,查看是否被更新呢

總結

觸發(fā)流程

  • NacosConfigService 內部有 ClientWorker 線程定時輪詢配置變化;
  • 當檢測到配置變更后,會回調配置監(jiān)聽器;
  • NacosContextRefresher 是 Spring Cloud Alibaba 提供的監(jiān)聽器;
  • 它觸發(fā) RefreshEvent 事件;
  • Spring Cloud Context 的 RefreshScope 監(jiān)聽 RefreshEvent
  • 清除舊 Bean 實例,下次注入重新構建。

到此這篇關于源碼分析Nacos如何動態(tài)刷新配置的文章就介紹到這了,更多相關Nacos動態(tài)刷新配置內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring深入刨析聲明式事務

    Spring深入刨析聲明式事務

    在spring注解中,使用聲明式事務,需要用到兩個核心的注解:@Transactional注解和@EnableTransactionManagement注解。將@Transactional注解加在方法上,@EnableTransactionManagement注解加在配置類上
    2022-12-12
  • MyBatisPlus分頁時排序的實現(xiàn)

    MyBatisPlus分頁時排序的實現(xiàn)

    本文主要介紹了MyBatisPlus分頁時排序的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Spring Boot與RabbitMQ結合實現(xiàn)延遲隊列的示例

    Spring Boot與RabbitMQ結合實現(xiàn)延遲隊列的示例

    本篇文章主要介紹了Spring Boot與RabbitMQ結合實現(xiàn)延遲隊列的示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Java繪圖技術的詳解及實例

    Java繪圖技術的詳解及實例

    這篇文章主要介紹了Java繪圖技術的詳解及實例的相關資料,這里主要詳解Graphics類的使用,需要的朋友可以參考下
    2017-08-08
  • @MapperScan注解與@Mapper注解的使用

    @MapperScan注解與@Mapper注解的使用

    這篇文章主要介紹了@MapperScan注解與@Mapper注解的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Maven在Java8下如何忽略Javadoc的編譯錯誤詳解

    Maven在Java8下如何忽略Javadoc的編譯錯誤詳解

    這篇文章主要給大家介紹了關于Maven在Java8下如何忽略Javadoc的編譯錯誤的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-08-08
  • Spring Boot使用Spring Mail發(fā)送郵件

    Spring Boot使用Spring Mail發(fā)送郵件

    在現(xiàn)代應用程序中,郵件通知是一種非常常見的需求,無論是用戶注冊成功后的歡迎郵件,還是系統(tǒng)異常時的報警郵件,都離不開郵件服務的支持,Spring Boot提供了簡便的方式Spring Mail來集成郵件發(fā)送功能,使得開發(fā)者能夠快速地為應用添加郵件發(fā)送能力
    2025-05-05
  • com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的區(qū)別及設定serverTimezone的方法

    com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的區(qū)

    這篇文章主要介紹了com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的區(qū)別以及設定serverTimezone的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-09-09
  • 詳解Springboot應用中設置Cookie的SameSite屬性

    詳解Springboot應用中設置Cookie的SameSite屬性

    Chrome 51 開始,瀏覽器的 Cookie 新增加了一個SameSite屬性,用來防止 CSRF 攻擊和用戶追蹤。今天通過本文給大家介紹Springboot應用中設置Cookie的SameSite屬性,感興趣的朋友一起看看吧
    2022-01-01
  • 解決Jackson反序列化map,set等復雜類型問題

    解決Jackson反序列化map,set等復雜類型問題

    這篇文章主要介紹了解決Jackson反序列化map,set等復雜類型問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09

最新評論

瑞安市| 平利县| 许昌县| 涿鹿县| 佳木斯市| 双鸭山市| 天镇县| 英超| 澄城县| 宾阳县| 交口县| 神木县| 郸城县| 安平县| 习水县| 上饶县| 喜德县| 新丰县| 南昌县| 财经| 浪卡子县| 镇雄县| 启东市| 屏山县| 密云县| 蓝山县| 汉中市| 宁夏| 洪洞县| 青川县| 陆良县| 商南县| 天津市| 阳曲县| 积石山| 衡阳县| 南宫市| 新巴尔虎左旗| 嘉善县| 长垣县| 康马县|