nacos入門系列之配置中心
之前學習了nacos註冊中心,今天繼續看看nacos的其他功能。 註冊中心連結
配置的釋出與訂閱
我們先來看看如何使用nacos提供的api來實現配置的釋出與訂閱
釋出配置:
public class ConfigPub { public static void main(String[] args) throws NacosException { final String dataId="test"; final String group="DEFAULT_GROUP"; ConfigService configService= NacosFactory.createConfigService("localhost:8848"); configService.publishConfig(dataId,group,"test config body"); } }
訂閱配置:
public static void main(String[] args) throws NacosException, InterruptedException { final String dataId="test"; final String group="DEFAULT_GROUP"; ConfigService configService= NacosFactory.createConfigService("localhost:8848"); configService.addListener(dataId, group, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { System.out.println("receiveConfigInfo:"+configInfo); } }); Thread.sleep(Integer.MAX_VALUE); } }
根據上面的demo可以看到透過dataId和group可以定位一個配置檔案。
深入瞭解配置釋出
1-釋出的配置資訊會透過http請求呼叫具體的服務
agent.httpPost(url, headers, params, encode, POST_TIMEOUT);
服務類為 ConfigController:處理配置相關的http請求
persistService .insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time, false); EventDispatcher.fireEvent( new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime()));
可以看到釋出的配置首先會進行持久化,然後會觸發變更通知。
持久化這裡就不做分析,我們來看看fireEvent這個方法:
EventDispatcher.fireEvent: static public void fireEvent(Event event) { if (null == event) { throw new IllegalArgumentException("event is null"); } for (AbstractEventListener listener : getEntry(event.getClass()).listeners) { try { listener.onEvent(event); } catch (Exception e) { log.error(e.toString(), e); } } } 這裡可以看到具體呼叫了listener.onEvent(event); 這裡只要找到AbstractEventListener 具體的實現類是哪個就可以。 AbstractEventListener主要有兩個實現類: AsyncNotifyService LongPollingService 我們可以透過event的型別去判斷,因為這裡onEvent的引數型別為ConfigDataChangeEvent, 所以我們可以清楚的知道我們要找的實現類是AsyncNotifyService。 每個AbstractEventListener初始化的時候都會先將自己加入到listeners中 final CopyOnWriteArrayList<AbstractEventListener> listeners; public AbstractEventListener() { /** * automatic register */ EventDispatcher.addEventListener(this); } 我們可以直接看看AsyncNotifyService的onEvent方法: public void onEvent(Event event) { // 併發產生 ConfigDataChangeEvent if (event instanceof ConfigDataChangeEvent) { ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event; long dumpTs = evt.lastModifiedTs; String dataId = evt.dataId; String group = evt.group; String tenant = evt.tenant; String tag = evt.tag; //Member{address='192.168.31.192:8848'} Collection<Member> ipList = memberManager.allMembers(); // 其實這裡任何型別佇列都可以 Queue<NotifySingleTask> queue = new LinkedList<NotifySingleTask>(); for (Member member : ipList) { queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, member.getAddress(), evt.isBeta)); } EXECUTOR.execute(new AsyncTask(httpclient, queue)); } } 上面的方法主要實現的是: 獲取所有的nacos服務節點,然後對其執行非同步任務AsyncTask。 AsyncTask中會從佇列中獲取每個節點的NotifySingleTask資訊,然後進行http請求,呼叫通知配置資訊改變 的服務。具體服務在CommunicationController中實現。 /** * 通知配置資訊改變 */ @GetMapping("/dataChange") 這個方法放在後面分析。
深入瞭解配置訂閱
初始化:
NacosConfigService初始化的時候構造了ClientWorker,並且透過ClientWorker啟動了兩個執行緒池。 worker = new ClientWorker(agent, configFilterChainManager, properties); 第一個執行緒池每10ms執行一次checkConfigInfo(); 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); 我們來看看checkConfigInfo具體是做什麼的 public void checkConfigInfo() { // 分任務 int listenerSize = cacheMap.get().size(); // 向上取整為批數,限制LongPollingRunnable處理配置的個數。 int longingTaskCount =(int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize()); if (longingTaskCount > currentLongingTaskCount) { for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) { // 要判斷任務是否在執行 這塊需要好好想想。 //任務列表現在是無序的。變化過程可能有問題 executorService.execute(new LongPollingRunnable(i)); //這裡的i就代表taskId } currentLongingTaskCount = longingTaskCount; } } 這裡主要的作用是提交LongPollingRunnable任務到第二個執行緒池中去執行。 並且每個LongPollingRunnable只會處理3000個配置。 我們來看看LongPollingRunnable的實現 List<CacheData> cacheDatas = new ArrayList<CacheData>(); List<String> inInitializingCacheList = new ArrayList<String>(); try { // check failover config for (CacheData cacheData : cacheMap.get().values()) { if (cacheData.getTaskId() == taskId) { cacheDatas.add(cacheData); ... } } cacheMap中儲存了配置資訊,從磁碟中載入獲取。 透過taskId從 cacheMap中獲取需要被當前LongPollingRunnable任務處理的配置,放入到cacheDatas集合。 我們來看看是在哪裡設定的taskId int taskId = cacheMap.get().size() / (int) ParamUtil.getPerTaskConfigSize(); cache.setTaskId(taskId); 可以看到這裡和上面相對應,每3000個配置的taskId是相同的。因為每個LongPollingRunnable執行緒會處理 3000個配置。 // check server config 向服務端請求變化的配置 List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList); //從Server獲取值變化了的DataID列表。返回的物件裡只有dataId和group是有效的。 保證不返回NULL。 return checkUpdateConfigStr(sb.toString(), isInitializingCacheList); 這裡訂閱配置的客戶端會向服務端傳送http長輪詢請求,來獲取變化的配置資訊 長輪詢請求不會立刻返回結果,而是當有配置發生變化時返回,設定了超時時間30s,如果超過了設定的 超時時間沒有配置更新,則會預設返回。然後重新發起一次長輪詢的請求。 HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params, agent.getEncode(), readTimeoutMs); 長輪詢的週期預設為30s: timeout=Math.max(NumberUtils.toInt(properties.getProperty(PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT), Constants.CONFIG_LONG_POLL_TIMEOUT), Constants.MIN_CONFIG_LONG_POLL_TIMEOUT); 具體服務實現類在ConfigController中: @PostMapping("/listener") @Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class) public void listener(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { .... // do long-polling inner.doPollingConfig(request, response, clientMd5Map, probeModify.length()); } doPollingConfig方法: // 服務端處理長輪詢請求 if (LongPollingService.isSupportLongPolling(request)) { longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize); return HttpServletResponse.SC_OK + ""; } 使用執行緒池處理請求: scheduler.execute( new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag)); 接著來看ClientLongPolling是一個執行緒實現類 首先會觸發一個延時任務,然後將自己加入到佇列:allSubs.add(this); allSubs中維護了所有長輪訓請求。 那麼肯定會有一個地方去消費allSubs佇列中的請求. 這個消費的地方就是onEvent方法: LongPollingService其實就是我們上面提到的AbstractEventListener,因此也實現了onEvent方法。 @Override public void onEvent(Event event) { if (isFixedPolling()) { // ignore } else { if (event instanceof LocalDataChangeEvent) { LocalDataChangeEvent evt = (LocalDataChangeEvent)event; scheduler.execute(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps)); } } } 這個event方法就是去處理配置變化的情況,主要邏輯在DataChangeTask中: 從allSubs獲取維護的請求中相同dataId+group的請求,比如:(test+DEFAULT_GROUP) 然後進行這個對長輪詢的請求進行返回。 for (Iterator<ClientLongPolling> iter = allSubs.iterator(); iter.hasNext(); ) { ClientLongPolling clientSub = iter.next(); //groupKey test+DEFAULT_GROUP if (clientSub.clientMd5Map.containsKey(groupKey)) { ...... iter.remove(); // 刪除訂閱關係 LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}", (System.currentTimeMillis() - changeTime), "in-advance", RequestUtil.getRemoteIp((HttpServletRequest)clientSub.asyncContext.getRequest()), "polling", clientSub.clientMd5Map.size(), clientSub.probeRequestSize, groupKey); clientSub.sendResponse(Arrays.asList(groupKey)); } } 那是哪裡觸發了LongPollingService裡面的onEvent 方法呢? 當然是在配置釋出後進行觸發的,還記得CommunicationController中的dataChange服務嗎? 配置釋出後會透過http請求呼叫nacos服務中的dataChange服務。透過dataChange服務就可以通知 nacos服務中儲存的長輪訓的請求了。 並且這個方法是獲取所有nacos服務節點去遍歷執行的,因此不管變更配置對應的長輪詢儲存在哪個節點, 都會可以被獲取到。 /** * 通知配置資訊改變 */ @GetMapping("/dataChange") 此處會呼叫DumpService中的方法儲存配置檔案到磁碟,並快取md5. DiskUtil.saveToDisk(dataId, group, tenant, content); public static void updateMd5(String groupKey, String md5, long lastModifiedTs) { CacheItem cache = makeSure(groupKey); if (cache.md5 == null || !cache.md5.equals(md5)) { cache.md5 = md5; cache.lastModifiedTs = lastModifiedTs; EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey)); } } 可以看到當配置變更,就會觸發fireEvent的LocalDataChangeEvent事件。
總結
到這裡,配置中心整體是實現基本上告一段落,還要很多細節沒有涉及到,需要在真正的使用過程中來探索和發現。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/75/viewspace-2825790/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java教程:nacos入門系列之配置中心Java
- Nacos配置中心原理
- SpringCloud Nacos 配置中心SpringGCCloud
- Docker 搭建 Nacos 配置中心Docker
- SpringCloud入門(五)Nacos註冊中心(上)SpringGCCloud
- SpringCloud入門(六)Nacos註冊中心(下)SpringGCCloud
- 【Nacos】微服務配置中心介紹微服務
- SpringBoot使用Nacos配置中心Spring Boot
- Springcloud alibaba nacos配置中心SpringGCCloud
- Nacos註冊中心+配置管理
- 配置中心之Nacos簡介,使用及Go簡單整合Go
- Spring Cloud Alibaba系列(二)nacos作為服務配置中心SpringCloud
- SpringCloud Alibaba入門之Nacos(SCA)SpringGCCloud
- springboot整合nacos註冊中心和配置中心Spring Boot
- springcloud alibaba-nacos配置中心SpringGCCloud
- nacos統一配置中心原始碼解析原始碼
- SpringCloud系列之分散式配置中心極速入門與實踐SpringGCCloud分散式
- Spring Cloud 從入門到精通(一)Nacos 服務中心初探SpringCloud
- 初探Nacos(四)-- SpringBoot下使用Nacos作為配置中心Spring Boot
- Spring Cloud Alibaba(5)---Nacos(配置中心)SpringCloud
- Solon2 專案整合 Nacos 配置中心
- Nacos註冊與配置中心:使用詳講
- 入門系列之在Nginx配置GzipNginx
- 保姆教程系列二、Nacos實現註冊中心
- ?【Alibaba中介軟體技術系列】「Nacos技術專題」配置中心載入原理和配置實時更新原理分析(中)
- ?【Alibaba中介軟體技術系列】「Nacos技術專題」配置中心載入原理和配置實時更新原理分析(上)
- 《xhtml入門系列》之四HTML
- Rust入門系列之切片Rust
- GreenDAO系列之(1)入門
- Nacos整合學習入門
- 阿里巴巴 Nacos 分散式配置中心原理阿里分散式
- 阿里 Nacos 註冊中心 配置啟動說明阿里
- Nacos配置中心叢集原理及原始碼分析原始碼
- Java入門系列之finalJava
- Java入門系列之重寫Java
- Nacos入門學習&實踐
- springboot工程dubbo使用nacos作為配置中心Spring Boot
- SpringBoot專案使用Nacos作為配置中心Spring Boot