Nacos配置中心交互模型是 push 還是 pull ?你應該這麼回答

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文案例收錄在 https://github.com/chengxy-nds/Springboot-Notebook","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家好,我是小富~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"大家應該都不太陌生,出身阿里名聲在外,能做動態服務發現、配置管理,非常好用的一個工具。然而這樣的技術用的人越多面試被問的概率也就越大,如果只停留在使用層面,那面試可能要喫大虧。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如我們今天要討論的話題,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"在做配置中心的時候,配置數據的交互模式是服務端推過來還是客戶端主動拉的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8c824870e269bb1a96e51a9dfec3d06a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我先拋出答案:客戶端主動拉的!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來咱們扒一扒","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"的源碼,來看看它具體是如何實現的?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置中心","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聊","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"之前簡單回顧下配置中心的由來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單理解配置中心的作用就是對配置統一管理,修改配置後應用可以動態感知,而無需重啓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲在傳統項目中,大多都採用靜態配置的方式,也就是把配置信息都寫在應用內的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yml","attrs":{}}],"attrs":{}},{"type":"text","text":"或","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"properties","attrs":{}}],"attrs":{}},{"type":"text","text":"這類文件中,如果要想修改某個配置,通常要重啓應用纔可以生效。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但有些場景下,比如我們想要在應用運行時,通過修改某個配置項,實時的控制某一個功能的開閉,頻繁的重啓應用肯定是不能接受的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尤其是在微服務架構下,我們的應用服務拆分的粒度很細,少則幾十多則上百個服務,每個服務都會有一些自己特有或通用的配置。假如此時要改變通用配置,難道要我挨個改幾百個服務配置?很顯然這不可能。所以爲了解決此類問題配置中心應運而生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/055b7a89bd97e0eae765b2244a4ca374.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"推與拉模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端與配置中心的數據交互方式其實無非就兩種,要麼推","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"push","attrs":{}}],"attrs":{}},{"type":"text","text":",要麼拉","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pull","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"推模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端與服務端建立","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TCP","attrs":{}}],"attrs":{}},{"type":"text","text":"長連接,當服務端配置數據有變動,立刻通過建立的長連接將數據推送給客戶端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優勢:長鏈接的優點是實時性,一旦數據變動,立即推送變更數據給客戶端,而且對於客戶端而言,這種方式更爲簡單,只建立連接接收數據,並不需要關心是否有數據變更這類邏輯的處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"弊端:長連接可能會因爲網絡問題,導致不可用,也就是俗稱的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"假死","attrs":{}}],"attrs":{}},{"type":"text","text":"。連接狀態正常,但實際上已無法通信,所以要有的心跳機制","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"KeepAlive","attrs":{}}],"attrs":{}},{"type":"text","text":"來保證連接的可用性,纔可以保證配置數據的成功推送。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"拉模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端主動的向服務端發請求拉配置數據,常見的方式就是輪詢,比如每3s向服務端請求一次配置數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輪詢的優點是實現比較簡單。但弊端也顯而易見,輪詢無法保證數據的實時性,什麼時候請求?間隔多長時間請求一次?都是不得不考慮的問題,而且輪詢方式對服務端還會產生不小的壓力。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"長輪詢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開篇我們就給出了答案,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"採用的是客戶端主動拉","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pull","attrs":{}}],"attrs":{}},{"type":"text","text":"模型,應用長輪詢(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Long Polling","attrs":{}}],"attrs":{}},{"type":"text","text":")的方式來獲取配置數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"額?以前只聽過輪詢,長輪詢又是什麼鬼?它和傳統意義上的輪詢(暫且叫短輪詢吧,方便比較)有什麼不同呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"短輪詢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不管服務端配置數據是否有變化,不停的發起請求獲取配置,比如支付場景中前段JS輪詢訂單支付狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣的壞處顯而易見,由於配置數據並不會頻繁變更,若是一直髮請求,勢必會對服務端造成很大壓力。還會造成推送數據的延遲,比如:每10s請求一次配置,如果在第11s時配置更新了,那麼推送將會延遲9s,等待下一次請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/26/2682e2cf5eb03137c3d04727aa04efaa.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決短輪詢的問題,有了長輪詢方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"長輪詢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"長輪詢可不是什麼新技術,它不過是由服務端控制響應客戶端請求的返回時間,來減少客戶端無效請求的一種優化手段,其實對於客戶端來說與短輪詢的使用並沒有本質上的區別。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端發起請求後,服務端不會立即返回請求結果,而是將請求掛起等待一段時間,如果此段時間內服務端數據變更,立即響應客戶端請求,若是一直無變化則等到指定的超時時間後響應請求,客戶端重新發起長鏈接。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/78/786cbb59a3753a11b5c74ecdd9399b13.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Nacos初識","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了後續演示操作方便我在本地搭了個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意:","attrs":{}},{"type":"text","text":" 運行時遇到個小坑,由於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"默認是以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cluster","attrs":{}}],"attrs":{}},{"type":"text","text":"集羣的方式啓動,而本地搭建通常是單機模式","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"standalone","attrs":{}}],"attrs":{}},{"type":"text","text":",這裏需手動改一下啓動腳本","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"startup.X","attrs":{}}],"attrs":{}},{"type":"text","text":"中的啓動模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fb/fbb7da470fe760067d7965ca5ab201e2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直接執行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/bin/startup.X","attrs":{}}],"attrs":{}},{"type":"text","text":"就可以了,默認用戶密碼均是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/006d1cd64d76537e89faa1d9b992a683.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"幾個概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的幾個核心概念:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"group","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"namespace","attrs":{}}],"attrs":{}},{"type":"text","text":",它們的層級關係如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/03/039a73915d949373554d7609c47473ca.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":":是配置中心裏最基礎的單元,它是一種","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"key-value","attrs":{}}],"attrs":{}},{"type":"text","text":"結構,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"key","attrs":{}}],"attrs":{}},{"type":"text","text":"通常是我們的配置文件名稱,比如:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"application.yml","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mybatis.xml","attrs":{}}],"attrs":{}},{"type":"text","text":",而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"value","attrs":{}}],"attrs":{}},{"type":"text","text":"是整個文件下的內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JSON","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"XML","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"YAML","attrs":{}}],"attrs":{}},{"type":"text","text":"等多種配置格式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/55/55393c35b94ff8970f39c2ebdadfa4bd.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"group","attrs":{}}],"attrs":{}},{"type":"text","text":":dataId配置的分組管理,比如同在dev環境下開發,但同環境不同分支需要不同的配置數據,這時就可以用分組隔離,默認分組","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DEFAULT_GROUP","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"namespace","attrs":{}}],"attrs":{}},{"type":"text","text":":項目開發過程中肯定會有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dev","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"test","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pro","attrs":{}}],"attrs":{}},{"type":"text","text":"等多個不同環境,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"namespace","attrs":{}}],"attrs":{}},{"type":"text","text":"則是對不同環境進行隔離,默認所有配置都在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"public","attrs":{}}],"attrs":{}},{"type":"text","text":"裏。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"架構設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖簡要描述了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的架構流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端、控制檯通過發送Http請求將配置數據註冊到服務端,服務端持久化數據到Mysql。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端拉取配置數據,並批量設置對","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"的監聽發起長輪詢請求,如服務端配置項變更立即響應請求,如無數據變更則將請求掛起一段時間,直到達到超時時間。爲減少對服務端壓力以及保證配置中心可用性,拉取到配置數據客戶端會保存一份快照在本地文件中,優先讀取。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fb/fbd2068955c4b154ec6d18bc3e2e7c93.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我省略了比較多的細節,如鑑權、負載均衡、高可用方面的設計(其實這部分纔是真正值得學的,後邊另出文講吧),主要弄清客戶端與服務端的數據交互模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下邊我們以Nacos 2.0.1版本源碼分析,2.0以後的版本改動較多,和網上的很多資料略有些不同地址:https://github.com/alibaba/nacos/releases/tag/2.0.1","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"客戶端源碼分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的客戶端源碼在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos-client","attrs":{}}],"attrs":{}},{"type":"text","text":"項目,其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NacosConfigService","attrs":{}}],"attrs":{}},{"type":"text","text":"實現類是所有操作的核心入口。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說之前先了解個客戶端數據結構","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":",這裏大家重點記住它,因爲它幾乎貫穿了Nacos客戶端的所有操作,由於存在多線程場景爲保證數據一致性,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"採用了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AtomicReference","attrs":{}}],"attrs":{}},{"type":"text","text":"原子變量實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * groupKey -> cacheData.\n */\nprivate final AtomicReference> cacheMap = new AtomicReference>(new HashMap<>());\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"是個Map結構,key爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupKey","attrs":{}}],"attrs":{}},{"type":"text","text":",是由dataId, group, tenant(租戶)拼接的字符串;value爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"對象,每個dataId都會持有一個CacheData對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"獲取配置","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"獲取配置數據的邏輯比較簡單,先取本地快照文件中的配置,如果本地文件不存在或者內容爲空,則再通過HTTP請求從遠端拉取對應dataId配置數據,並保存到本地快照中,請求默認重試3次,超時時間3s。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6a/6a023ee7326031840b8ed2e130a75752.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取配置有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfig()","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfigAndSignListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"這兩個接口,但","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfig()","attrs":{}}],"attrs":{}},{"type":"text","text":"只是發送普通的HTTP請求,而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfigAndSignListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"則多了發起長輪詢和對dataId數據變更註冊監聽的操作","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addTenantListenersWithContent()","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic String getConfig(String dataId, String group, long timeoutMs) throws NacosException {\n return getConfigInner(namespace, dataId, group, timeoutMs);\n}\n\n@Override\npublic String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)\n throws NacosException {\n String content = getConfig(dataId, group, timeoutMs);\n worker.addTenantListenersWithContent(dataId, group, content, Arrays.asList(listener));\n return content;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"註冊監聽","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端註冊監聽,先從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"中拿到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"對應的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void addTenantListenersWithContent(String dataId, String group, String content,\n List extends Listener> listeners) throws NacosException {\n group = blank2defaultGroup(group);\n String tenant = agent.getTenant();\n // 1、獲取dataId對應的CacheData,如沒有則向服務端發起長輪詢請求獲取配置\n CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);\n synchronized (cache) {\n // 2、註冊對dataId的數據變更監聽\n cache.setContent(content);\n for (Listener listener : listeners) {\n cache.addListener(listener);\n }\n cache.setSyncWithServer(false);\n agent.notifyListenConfig();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如沒有則向服務端發起長輪詢請求獲取配置,默認的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Timeout","attrs":{}}],"attrs":{}},{"type":"text","text":"時間爲30s,並把返回的配置數據回填至","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"對象的content字段,同時用content生成MD5值;再通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"註冊監聽器。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8a/8a979dfccaec6e8135473a69dde09a9c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"也是個出場頻率非常高的一個類,我們看到除了dataId、group、tenant、content這些相關的基礎屬性,還有幾個比較重要的屬性如:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"listeners","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"md5","attrs":{}}],"attrs":{}},{"type":"text","text":"(content真實配置數據計算出來的md5值),以及註冊監聽、數據比對、服務端數據變更通知操作都在這裏。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bf/bf5c38f35c76276aadf9abd268366b3f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"listeners","attrs":{}}],"attrs":{}},{"type":"text","text":"是對dataId所註冊的所有監聽器集合,其中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ManagerListenerWrap","attrs":{}}],"attrs":{}},{"type":"text","text":"對象除了持有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Listener","attrs":{}}],"attrs":{}},{"type":"text","text":"監聽類,還有一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lastCallMd5","attrs":{}}],"attrs":{}},{"type":"text","text":"字段,這個屬性很關鍵,它是判斷服務端數據是否更變的重要條件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在添加監聽的同時會將","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"對象當前最新的md5值賦值給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ManagerListenerWrap","attrs":{}}],"attrs":{}},{"type":"text","text":"對象的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lastCallMd5","attrs":{}}],"attrs":{}},{"type":"text","text":"屬性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void addListener(Listener listener) {\n ManagerListenerWrap wrap =\n (listener instanceof AbstractConfigChangeListener) ? new ManagerListenerWrap(listener, md5, content)\n : new ManagerListenerWrap(listener, md5);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到這對dataId監聽設置就完事了?我們發現所有操作都圍着","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"結構中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"對象,那麼大膽猜測下一定會有專門的任務來處理這個數據結構。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc89b88a8bb5044641f73bc647b3d145.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"變更通知","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端又是如何感知服務端數據已變更呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們還是從頭看,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NacosConfigService","attrs":{}}],"attrs":{}},{"type":"text","text":"類的構造器中初始化了一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientWorker","attrs":{}}],"attrs":{}},{"type":"text","text":",而在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientWorker","attrs":{}}],"attrs":{}},{"type":"text","text":"類的構造器中又啓動了一個線程池來輪詢","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/39/3912cec9242a2d1b688c1093bc6529a8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"executeConfigListen()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法中有這麼一段邏輯,檢查","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"中dataId的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"對象內,MD5字段與註冊的監聽","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"listener","attrs":{}}],"attrs":{}},{"type":"text","text":"內的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lastCallMd5值","attrs":{}}],"attrs":{}},{"type":"text","text":",不相同表示配置數據變更則觸發","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"safeNotifyListener","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,發送數據變更通知。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"void checkListenerMd5() {\n for (ManagerListenerWrap wrap : listeners) {\n if (!md5.equals(wrap.lastCallMd5)) {\n safeNotifyListener(dataId, group, content, type, md5, encryptedDataKey, wrap);\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"safeNotifyListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法單獨起線程,向所有對","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"註冊過監聽的客戶端推送變更後的數據內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/547b20857ac988a38a53582451ea99a3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端接收通知,直接實現","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"receiveConfigInfo()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法接收回調數據,處理自身業務就可以了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"configService.addListener(dataId, group, new Listener() {\n @Override\n public void receiveConfigInfo(String configInfo) {\n System.out.println(\"receive:\" + configInfo);\n }\n\n @Override\n public Executor getExecutor() {\n return null;\n }\n});\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了理解更直觀我用測試demo演示下,獲取服務端配置並設置監聽,每當服務端配置數據變化,客戶端監聽都會收到通知,一起看下效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String[] args) throws NacosException, InterruptedException {\n String serverAddr = \"localhost\";\n String dataId = \"test\";\n String group = \"DEFAULT_GROUP\";\n Properties properties = new Properties();\n properties.put(\"serverAddr\", serverAddr);\n ConfigService configService = NacosFactory.createConfigService(properties);\n String content = configService.getConfig(dataId, group, 5000);\n System.out.println(content);\n configService.addListener(dataId, group, new Listener() {\n @Override\n public void receiveConfigInfo(String configInfo) {\n System.out.println(\"數據變更 receive:\" + configInfo);\n }\n @Override\n public Executor getExecutor() {\n return null;\n }\n });\n\n boolean isPublishOk = configService.publishConfig(dataId, group, \"我是新配置內容~\");\n System.out.println(isPublishOk);\n\n Thread.sleep(3000);\n content = configService.getConfig(dataId, group, 5000);\n System.out.println(content);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結果和預想的一樣,當向服務端","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"publishConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"數據變化後,客戶端可以立即感知,愣是用主動拉","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pull","attrs":{}}],"attrs":{}},{"type":"text","text":"模式做出了服務端實時推送的效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"數據變更 receive:我是新配置內容~\ntrue\n我是新配置內容~\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"服務端源碼分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的服務端源碼主要在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos-config","attrs":{}}],"attrs":{}},{"type":"text","text":"項目的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ConfigController","attrs":{}}],"attrs":{}},{"type":"text","text":"類,服務端的邏輯要比客戶端稍複雜一些,這裏我們重點看下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"處理長輪詢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務端對外提供的監聽接口地址","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/v1/cs/configs/listener","attrs":{}}],"attrs":{}},{"type":"text","text":",這個方法內容不多,順着","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"doPollingConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"往下看。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/30/30f4ed492c6964f96694c318c34e0257.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務端根據請求","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"header","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Long-Pulling-Timeout","attrs":{}}],"attrs":{}},{"type":"text","text":"屬性來區分請求是長輪詢還是短輪詢,這裏咱們只關注長輪詢部分,接着看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LongPollingService","attrs":{}}],"attrs":{}},{"type":"text","text":"(記住這個service很關鍵)類中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addLongPollingClient()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法是如何處理客戶端的長輪詢請求的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4b/4b0495887953464ee50aa9800289c0ca.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正常客戶端默認設置的請求超時時間是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"30s","attrs":{}}],"attrs":{}},{"type":"text","text":",但這裏我們發現服務端“偷偷”的給減掉了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"500ms","attrs":{}}],"attrs":{}},{"type":"text","text":",現在超時時間只剩下了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"29.5s","attrs":{}}],"attrs":{}},{"type":"text","text":",那爲什麼要這樣做呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用官方的解釋之所以要提前500ms響應請求,爲了最大程度上保證客戶端不會因爲網絡延時造成超時,考慮到請求可能在負載均衡時會耗費一些時間,畢竟","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"最初就是按照阿里自身業務體量設計的嘛!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/00267f3c087c82c7924f85b1ffb78b1a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時對客戶端提交上來的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupkey","attrs":{}}],"attrs":{}},{"type":"text","text":"的MD5與服務端當前的MD5比對,如","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"md5","attrs":{}}],"attrs":{}},{"type":"text","text":"值不同,則說明服務端的配置項發生過變更,直接將該","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupkey","attrs":{}}],"attrs":{}},{"type":"text","text":"放入","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"changedGroupKeys","attrs":{}}],"attrs":{}},{"type":"text","text":"集合並返回給客戶端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"MD5Util.compareMd5(req, rsp, clientMd5Map)\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如未發生變更,則將客戶端請求掛起,這個過程先創建一個名爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling","attrs":{}}],"attrs":{}},{"type":"text","text":"的調度任務","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Runnable","attrs":{}}],"attrs":{}},{"type":"text","text":",並提交給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"scheduler","attrs":{}}],"attrs":{}},{"type":"text","text":"定時線程池延後","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"29.5s","attrs":{}}],"attrs":{}},{"type":"text","text":"執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ConfigExecutor.executeLongPolling(\n new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag));\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏每個長輪詢任務攜帶了一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"asyncContext","attrs":{}}],"attrs":{}},{"type":"text","text":"對象,使得每個請求可以延遲響應,等延時到達或者配置有變更之後,調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"asyncContext.complete()","attrs":{}}],"attrs":{}},{"type":"text","text":"響應完成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"asyncContext 爲 Servlet 3.0新增的特性,異步處理,使Servlet線程不再需要一直阻塞,等待業務處理完畢才輸出響應;可以先釋放容器分配給請求的線程與相關資源,減輕系統負擔,其響應將被延後,在處理完業務或者運算後再對客戶端進行響應。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0a/0a94a301e96c110bf0f6093d8fee5fe9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling","attrs":{}}],"attrs":{}},{"type":"text","text":"任務被提交進入延遲線程池執行的同時,服務端會通過一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"隊列保存所有正在被掛起的客戶端長輪詢請求任務,這個是客戶端註冊監聽的過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如延時期間客戶端據數一直未變化,延時時間到達後將本次長輪詢任務從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"隊列剔除,並響應請求","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"response","attrs":{}}],"attrs":{}},{"type":"text","text":",這是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"取消監聽","attrs":{}}],"attrs":{}},{"type":"text","text":"。收到響應後客戶端再次發起長輪詢,循環往復。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/995d70e714ff0cfbfa707f2617e7dff9.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這我們知道服務端是如何掛起客戶端長輪詢請求的,一旦請求在掛起期間,用戶通過管理平臺操作了配置項,或者服務端收到了來自其他客戶端節點修改配置的請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"怎麼能讓對應已掛起的任務立即取消,並且及時通知客戶端數據發生了變更呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"數據變更","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"管理平臺或者客戶端更改配置項接位置","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ConfigController","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"publishConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab33d3772da6bbdd54d99b5616ffde62.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"值得注意得是,在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"publishConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"接口中有這麼一段邏輯,某個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"配置數據被修改時會觸發一個數據變更事件","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Event","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime()));\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"仔細看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LongPollingService","attrs":{}}],"attrs":{}},{"type":"text","text":"會發現在它的構造方法中,正好訂閱了數據變更事件,並在事件觸發時執行一個數據變更調度任務","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DataChangeTask","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/46c645877b540e5b4dc0a0198763d5b0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"DataChangeTask","attrs":{}}],"attrs":{}},{"type":"text","text":"內的主要邏輯就是遍歷","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"隊列,上邊我們知道,這個隊列中維護的是所有客戶端的長輪詢請求任務,從這些任務中找到包含當前發生變更的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupkey","attrs":{}}],"attrs":{}},{"type":"text","text":"的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling","attrs":{}}],"attrs":{}},{"type":"text","text":"任務,以此實現數據更變推送給客戶端,並從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"隊列中剔除此長輪詢任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc4b04499b1abd4a285eb83f42194067.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而我們在看給客戶端響應","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"response","attrs":{}}],"attrs":{}},{"type":"text","text":"時,調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"asyncContext.complete()","attrs":{}}],"attrs":{}},{"type":"text","text":"結束了異步請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/801c446d177af3b53d897f7a633ce518.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"結束語","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上邊只揭開了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的冰山一角,實際上還有非常多重要的技術細節都沒提及到,建議大家沒事看看源碼,源碼不需要通篇的看,只要抓住核心部分就夠了。就比如今天這個題目以前我真沒太在意,突然被問一下子喫不準了,果斷看下源碼,而且這樣記憶比較深刻(別人嚼碎了餵你的知識總是比自己咀嚼的差那麼點意思)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"的源碼我個人覺得還是比較","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"樸素","attrs":{}},{"type":"text","text":"的,代碼並沒有過多炫技,看起來相對輕鬆。大家不要對看源碼有什麼牴觸,它也不過是別人寫的業務代碼而已,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"just so so","attrs":{}},{"type":"text","text":"!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"我是小富~","attrs":{}},{"type":"text","text":",如果對你有用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在看","attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"關注","attrs":{}},{"type":"text","text":"支持下,咱們下期見~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整理了幾百本各類技術電子書,有需要的同學自取。技術羣快滿了,想進的同學可以加我好友,和大佬們一起吹吹技術。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/chengxy-nds/Springboot-Notebook","title":"","type":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"電子書地址","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"個人公衆號: ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"程序員內點事","attrs":{}},{"type":"text","text":",歡迎交流","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章