nacos 本地测试_Nacos注册中心落地实践
前言
公司在19年開始推進同城雙活架構,未來規劃是在南匯機房出現故障時能把所有讀流量切到寶山機房,這樣至少保證讀請求是沒問題的;我們的微服務使用的zookeeper來做服務發現, zk由于它的強一致性模型不適合多機房部署, 由于zk的服務發現模型是基于會話機制創建的臨時節點, 就算兩個機房各部署一套zk, 再部署一個sync服務兩邊同步,也會因為跨機房網絡不穩定導致連接斷開, zk會因此一開始就沒有考慮繼續用zk作為下一代服務發現的注冊中心.
zk基于ZAB協議實現了強一致性,在出現網絡分區時有可能因集群無法選出Leader導致服務不可用, 這時候不可以新部署,重新啟動,擴容或者縮容,這對于服務發現場景來講是不能接受的, 在實踐中,注冊中心不能因為自身的任何原因破壞服務之間本身的可連通性, 數據的短期不一致對客戶端來說就是多一個或者少一個節點,對于業務層面完全是可以接受的, 因此注冊中心在設計時應偏向AP架構.
背景故事
我剛入職的時候便接手了一個任務:把zk注冊中心替換成consul,因為網上的介紹都說consul是AP架構, 而且consul在生產環境已經完全落地, 運維已經用了consul來做物理機的發現和監控, 因此當時的做法就是研究consul的接入文檔和API, 完成了基于consul的開發和驗證,上線了一個注冊中心同步的服務,用于把zk注冊中心和consul雙向同步, 上線后很多服務開始報no active server的錯誤,最后排查下來發現我們線上的consul版本并不支持tag過濾的功能, 導致consul返回了在測試環境不會返回的服務實例信息, 最后導致反復觸發服務上線下線事件, 進而引起了故障,最后把sync服務強制下線才解決; 這次事故還發現個問題是線上的prometheus會給所有thrift端口發送/metric http請求, 導致m2服務解析時直接內存溢出了, 需要重啟服務才能解決.
這次事故我們吸取了深刻的教訓, 由于沒有深入了解Consul的原理, 導致解決問題花了較長時間,后面我們便把進度放慢, 先去熟悉Consul的技術架構, 深入Consul的源碼才發現,Consul并不是AP架構, Consul的服務注冊模型和KV模型都是基于Raft實現的強一致性,因此對于服務發現它是一個CP的架構, 而Consul內部Agent的發現和事件機制是基于Gossip流言協議實現的, 這個協議是最終一致性的,所以網上很多文章把Consul歸類為AP架構, 這個是有問題的.
還有一個暴露出來的問題是運維用Consul來管理物理機,他們對可用性,一致性,性能這些指標并沒有太多的要求, 因此業務和運維共用一套服務發現系統是有很大的風險的, 運維測試環境和生產環境安裝的consul版本不一致就是如此, 因此基于一致性模型和數據共享的風險我們最終棄用了Consul.
注冊中心選型
Eureka調研
基于我們在服務發現領域的積累, 最終一致性的架構比較適合注冊中心的場景,因此我們調研了業界使用比較廣泛的2款開源框架Eureka和Nacos.
Eureka是Netflix開源的一款提供服務注冊和發現的產品,基于Java語言開發,在它的實現中,節點之間相互平等,部分注冊中心的節點掛掉也不會對集群造成影響,即使集群只剩一個節點存活,也可以正常提供發現服務。哪怕是所有的服務注冊節點都掛了,Eureka Clients上也會緩存服務調用的信息。這就保證了我們微服務之間的互相調用足夠健壯.
看完前面的介紹可能會覺得Eureka的實現非常適合服務發現的場景,在SpringCloud體系下Eureka在很多公司都有成功的落地經驗, 但深入Eureka源碼層面進行分析時,我們發現了Eureka的一個巨大隱患, Eureka每個服務節點會保存所有的服務實例數據, 同時每個Eureka
會作為其他節點的Client, Eureka-Client也會保存一份全量的數據, 這就導致每個Eureka Server保存了2份全量的數據,因此當集群規模比較大時Eureka Server的壓力會非常大,而且無法通過水平擴展來分擔壓力.
貼一張Eureka的官方架構圖:
總結了一下Eureka存在的問題
訂閱端拿到的是所有服務的全量地址,這個對于客戶端的內存是一個比較大的消耗(不使用官方客戶端可以解決),每個server節點會在內存里保存2份全量的注冊信息
pull模式:客戶端采用周期性pull方式存在實時性不足以及拉取性能消耗的問題(開發長輪詢功能)
一致性協議:Eureka集群的多副本的一致性協議采用類似“異步多寫”的AP協議,每一個 server都會把本地接收到的寫請求(register/heartbeat/unregister/update)發送給組成集群的其他所有的機器,特別是hearbeat報文是周期性持續不斷的在client->server->all other server之間傳送
當讀請求增多的時候集群需要擴容,但擴容又會導致每臺server承擔更多的寫請求,擴容效果并不明顯,而且每個server內存中保存2份全量的數據,內存會成為瓶頸
1.x版本更新頻率很低,2.x版本閉源, 問題修復頻率低
Nacos調研
除了Eureka之外,我們還調研了另一款采用AP架構的Nacos注冊中心,Nacos由阿里開源,在國內很多公司有成功落地經驗,由于Nacos包含注冊中心和配置中心兩部分功能,以下內容只涉及注冊中心.
nacos總體思路和eureka差不多,但是解決了eureka存在的一些問題:
pull時效性問題:采用udpPush+ack的方式, 并且支持定時pull
心跳請求在server間來回轉發的問題:采用分片方式每個server負責一部分service的狀態檢查,并定時向其他server同步
擴容效果不明顯問題:采用hash分片機制,每個server處理一部分service, 雖然每個server存儲的數據量是一樣的,但擴容可以解決寫入的瓶頸
除此之外Nacos的其他幾個關鍵特性:
支持多個namespace,實現租戶隔離
支持CP和AP兩種一致性模式, CP模式也是采用的Raft協議
支持主動和被動的心跳檢測方式, 主動檢測支持TCP,HTTP和MySQL協議, 由Nacos Server主動向服務實例發送探測請求來更新節點的健康狀態,主要用來監控那些持久的節點,比如數據庫,Redis實例等, 被動檢測是由節點主動通過HTTP接口向Nacos Server發送心跳請求
下面這個是Nacos Server的請求處理流轉圖
綜合對比后,我們決定用Nacos來作為我們微服務的下一代注冊中心.
Nacos開發篇
官方的Nacos還無法直接在我們的生產環境上使用, 因為Nacos采用的Udp Push的方式來更新服務實例, 而Udp Push需要每個服務實例多監聽一個端口,我們線上基本都是一個物理機部署好幾個服務, 采用監聽端口的方式很容易就端口沖突了, 因此我們需要將Udp Push改造成HTTP 長輪詢的方式;
Nacos Server驗證和改造
HTTP長輪詢改造
Http長輪詢功能是基于Servlet 3.0 提供的AsyncContext的能力, nacos-client收到訂閱請求后會和nacos-server建立HTTP長連接, nacos只有在服務實例有變更時才向客戶端返回最新的數據, 延遲基本在ms級別.
MetaServer改造
Nacos Server之間互相發現是通過配置文件里配置服務列表實現的,如果要新增一個節點, 需要修改配置文件并重啟所有節點; 因此為了后面方便快速擴容, 我們把Server列表接入了配置中心,可以動態更新server集群的地址;
Nacos-Client默認也是通過配置的方式獲取獲取server集群地址, 同時也支持通過vipServer的方式來動態刷新服務列表, 需要客戶端配置一個http url, 由于我們線上物理機都安裝了Consul Agent, 因此我們直接把Consul當做MetaServer, 當health server列表有變更時把變更后的地址寫到Consul的KV里, Nacos-Client動態的從Consul KV獲取healthy集群的地址.
壓測和斷網測試
Nacos官網給出的壓測結果是非常優秀的, 我們壓測下來也發現線上只需要部署三個節點就能滿足要求,Nacos的代碼質量也比較高, 服務GC一直都比較穩定, 最后我們線上是部署了5個節點.
由于Nacos采用的是AP架構, 我們需要重點關注出現網絡分區或機器故障的場景下Nacos的實際表現, 因此針對各種可能出現的場景都做了驗證,下面列出來的是發現問題的幾個場景:
測試場景1:網絡分區5分鐘,網絡恢復后2個server數據一直會不一致
最后發現是Nacos的同步任務有Bug,該Bug已提交PR并采納,參考issue1665
測試場景2: 兩個實例網絡分區后服務實例的變化過程
由于采用的分片處理的模型, 當某一個節點和其他節點網絡分區了并且15s內沒有恢復, 這個節點上的實例會集體下線幾秒鐘,客戶端會出現大量的no active server, 這個問題我在Github與Nacos團隊有討論過, 參考issue1873,他們給出的解決方案是Nacos Server之間剔除過期節點的時間間隔要小于 (服務實例超時時間-服務實例心跳間隔)
后面我們修改了Nacos Server之間的心跳配置,改為每秒發送一次心跳, 超過10s沒有收到心跳就摘除該Server.
測試場景3: Nacos Server重啟后服務實例的變化
Nacos Server啟動后會從其他Server同步全量的數據, 但如果同步過來的部分服務原先是由自己負責處理心跳的, 而這些實例的心跳時間戳是比較早的(Nacos-Server之間并不會同步服務實例最新的heartbeat時間戳), 這些服務會被每5s執行的ClientBeatCheckTask刪除掉,但很快會重新注冊上.
如果不在業務高峰期重啟一般問題不大, 但想了下還是加了個保護模式, 當Nacos-Server集群地址有變更時, 暫停主動的ClientBeatCheckTask 30s, 這樣Nacos Server可以無損的發布和重啟, 重啟Nacos Server對業務完全無影響.
雙注冊中心架構
Nacos注冊中心上線后, 我們需要考慮的是和現有zk注冊中心的兼容問題, 由于我們線上還有很多使用服務還在使用老版本微服務sdk, 這些服務很多都找不到維護的人, 因此我們預計到未來很長一段時間內需要保留2個注冊中心, 因此需要有個服務對2個注冊中心的數據進行雙向同步.
Nacos官方提供了Nacos-Sync來做注冊中心數據的遷移, 但調研后發現這是一個單機版的實現,主要用于一次性遷移數據, 無法達到高可用, 因此我們需要自己開發一個Sync服務,用于zk和nacos雙向同步數據.
Sync服務處理的原則:
無狀態, 服務實例無論是注冊在zk還是nacos,兩個注冊中心的數據必須是一致的, 而且允許中間狀態的存在(在升級過程中一個服務可能部分實例用zk,部分用nacos)
一個業務服務在絕大多數情況下,一般只存在一個雙向同步任務, 在sync服務上下線過程中可能reHash出現多個sync節點都存在同一個服務的sync任務,但結果必須是一致的
一個業務服務的同步方向,是根據業務服務實例元數據( Metadata )的標記 fromSync 來決定的,比如服務實例是注冊在zk, 該實例同步到nacos后會加一個fromSync的
MetaData, 這樣從Nacos同步到zk時會忽略fromSync=true的實例,避免來回同步
我們采用了一致性 Hash 方式來解決任務分配的問題,當一臺或者幾臺同步服務器掛掉后,采用 Zookeeper 臨時節點的 Watch 機制監聽同步服務器掛掉情況,通知剩余同步服務器執行 reHash,掛掉服務的工作由剩余的同步服務器來承擔,通過一致性 Hash 實現被同步的業務服務列表的平均分配,基于對業務服務名的二進制轉換作為 Hash 的 Key 實現一致性 Hash 的算法.
Sync服務在給Nacos發送心跳時, 將心跳上報請求放入隊列,以固定線程消費,當一個sync節點處理的服務實例數超過一定的閾值會造成業務服務實例的心跳發送不及時,從而造成業務服務實例的意外丟失。其實對于sync過去的服務來說, 心跳的間隔可以設置的長一些, 因為服務實例一旦在一個注冊中心下線了, 會被sync服務監聽到然后主動下線,這些實例并不需要nacos server頻繁的主動剔除檢查, 因此我們實際采用了一個15~45s的隨機值作為心跳間隔, 極大的減少了sync服務發送心跳的壓力,同一個服務的所有實例也不會出現集體下線的情況.
Sync服務平滑上下線
由于我們依賴zk的臨時節點來做一致Hash, 當某個sync節點下線時,其他節點監聽到該節點下線時有延遲的, 這樣就會出現sync過去的實例會有1s左右的下線, 針對這個場景,我們實現了主動下線的邏輯, 在重啟sync節點前發送主動下線命令, 該節點主動把自己從zk上摘除,并執行慢清理操作, 這樣在該節點停止之前其他節點能把對應的syncTask接管過去, 實現無損下線.
最后我們的雙注冊中心架構如下:
總結
以上是生活随笔為你收集整理的nacos 本地测试_Nacos注册中心落地实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python文本分词_【Python】使
- 下一篇: amd插帧技术如何开启_联想ThinkP