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

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

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

一 Nacos 刷新配置的源碼閱讀

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

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))) {
                       // 檢查當(dāng)前 配置文件的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);
            }
        }
    }

檢查當(dāng)前的md5值是否更改

void checkListenerMd5() {
        for (ManagerListenerWrap wrap : listeners) {
            if (!md5.equals(wrap.lastCallMd5)) {
              // 如果md5值變了,就發(fā)送對(duì)應(yīng)事件通知
                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)建一個(gè)通知任務(wù)(異步或同步執(zhí)行)
    Runnable job = new Runnable() {
        @Override
        public void run() {
            // 當(dāng)前線程的原始類加載器
            ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
            // 獲取監(jiān)聽器所屬類的類加載器(用于類加載隔離)
            ClassLoader appClassLoader = listener.getClass().getClassLoader();

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

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

                // 構(gòu)造配置響應(yīng)對(duì)象
                ConfigResponse cr = new ConfigResponse();
                cr.setDataId(dataId);
                cr.setGroup(group);
                cr.setContent(content);

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

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

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

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

                    // 記錄這次通知的內(nèi)容
                    listenerWrap.lastContent = content;
                }

                // 更新上一次調(diào)用的 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 {
                // 恢復(fù)原始線程類加載器,避免線程池復(fù)用帶來問題
                Thread.currentThread().setContextClassLoader(myClassLoader);
            }
        }
    };

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

    // 記錄通知完成時(shí)間
    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 注冊(cè)一個(gè) Nacos 配置監(jiān)聽器
 * @param groupKey 配置分組(group)
 * @param dataKey 配置標(biāo)識(shí)(dataId)
 */
private void registerNacosListener(final String groupKey, final String dataKey) {
    // 生成一個(gè)唯一 key,用于標(biāo)識(shí)監(jiān)聽器(key = group + "++" + dataId)
    String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);

    // 從 listenerMap 中獲取對(duì)應(yīng) key 的監(jiān)聽器,如果不存在則創(chuàng)建一個(gè) AbstractSharedListener
    Listener listener = listenerMap.computeIfAbsent(key,
        lst -> new AbstractSharedListener() {
            /**
             * 當(dāng)配置變更時(shí),會(huì)觸發(fā)該方法
             */
            @Override
            public void innerReceive(String dataId, String group, String configInfo) {
                // 刷新次數(shù) +1(用于監(jiān)控/統(tǒng)計(jì))
                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 {
        // 調(diào)用 Nacos 客戶端 API,注冊(cè)監(jiān)聽器
        configService.addListener(dataKey, groupKey, listener);
    }
    catch (NacosException e) {
        // 注冊(cè)失敗,記錄警告日志
        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 方法刷新所有作用域?yàn)?refresh 的bean

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

二 @refreshScope注解

定義

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

使用背景

Spring Boot 默認(rèn)的 Bean 是單例的(@Singleton),一旦初始化完成,其屬性就不會(huì)再變化。如果你想在運(yùn)行時(shí)通過配置中心動(dòng)態(tài)刷新某個(gè) 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 // 開啟動(dòng)態(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中的配置,查看是否被更新呢

總結(jié)

觸發(fā)流程

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

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

相關(guān)文章

  • Spring深入刨析聲明式事務(wù)

    Spring深入刨析聲明式事務(wù)

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

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

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

    Spring Boot與RabbitMQ結(jié)合實(shí)現(xiàn)延遲隊(duì)列的示例

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

    Java繪圖技術(shù)的詳解及實(shí)例

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

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

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

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

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

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

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

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

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

    詳解Springboot應(yīng)用中設(shè)置Cookie的SameSite屬性

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

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

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

最新評(píng)論

曲阜市| 化隆| 扎兰屯市| 区。| 托克托县| 郯城县| 湄潭县| 科技| 运城市| 公安县| 上思县| 易门县| 深圳市| 大足县| 富民县| 贞丰县| 贵阳市| 海南省| 永州市| 孟州市| 茌平县| 明水县| 浑源县| 和硕县| 双辽市| 巨鹿县| 张掖市| 龙州县| 安平县| 贞丰县| 吉安县| 西乌珠穆沁旗| 辰溪县| 武安市| 施秉县| 方正县| 岳普湖县| 年辖:市辖区| 革吉县| 安吉县| 吴旗县|