背景
產(chǎn)線nacos集群硬件比較差固以,cpu只有2核墩虹,出現(xiàn)了一次cpu滿,導(dǎo)致服務(wù)調(diào)用失敗嘴纺,復(fù)盤(pán)后败晴,要進(jìn)行硬件升級(jí)浓冒,升到4核栽渴,先停1臺(tái),觀察稳懒,剛開(kāi)始沒(méi)有影響闲擦,以為穩(wěn)了,沒(méi)有想到過(guò)了3分鐘场梆,業(yè)務(wù)監(jiān)控指標(biāo)就不行了墅冷,服務(wù)出現(xiàn)調(diào)用失敗,報(bào)No provider 異常,立即重新拉起來(lái)或油,因?yàn)橄戮€一臺(tái)寞忿,就導(dǎo)致1分多鐘的線上故障,一身冷汗啊!!!
nacos作為服務(wù)注冊(cè)中心顶岸,不是AP嗎腔彰,怎么停一臺(tái)就有問(wèn)題。不合理啊辖佣。
為啥剛停沒(méi)有問(wèn)題霹抛,等了3分鐘才出現(xiàn)問(wèn)題,為啥啊
帶著這兩個(gè)問(wèn)題和錯(cuò)誤日志卷谈,花了幾天的時(shí)間排查杯拐,總算是把問(wèn)題弄清楚了,也順便把nacos的同步機(jī)制整明白了世蔗。
日志現(xiàn)象
重新拉起來(lái)后端逼,就立即開(kāi)始了故障分析,因?yàn)闆](méi)有其他異常污淋,就從錯(cuò)誤日志開(kāi)始排查裳食,我們集群三個(gè)節(jié)點(diǎn),發(fā)現(xiàn)另外兩臺(tái)在停掉一臺(tái)3分鐘后芙沥,都出現(xiàn)大批量的鏈接超時(shí)诲祸,刪除了鏈接對(duì)應(yīng)的client浊吏,從而觸發(fā)了推送,導(dǎo)致客戶端沒(méi)有了節(jié)點(diǎn)可用救氯。
客戶端空保護(hù)
dubbo consumr在路由時(shí)找田,如果對(duì)打tag的機(jī)器做路由不能給正常的機(jī)器使用,即使只剩下一臺(tái)打tag的機(jī)器着憨,也不能用墩衙,反而對(duì)tag的流量可用用非tag的機(jī)器,即做了降級(jí)甲抖,是不是很難理解漆改,不為正常的流量做降級(jí),反而為灰度的這點(diǎn)流量做了兜底准谚,個(gè)人覺(jué)得這個(gè)設(shè)計(jì)很坑挫剑,因?yàn)槲覀兙€上都會(huì)有一臺(tái)灰度的機(jī)器,nacos推送的時(shí)候柱衔,最后存活的是這個(gè)灰度的機(jī)器樊破,consumer 端就game over了,即出現(xiàn)no provider錯(cuò)誤唆铐。
同步原理
經(jīng)過(guò)日志分析哲戚,最終定位是同步延遲導(dǎo)致的,但為啥同步會(huì)延遲了艾岂,同步的機(jī)制和原理又是怎么樣的顺少,你一定又這些疑問(wèn),下面我們就來(lái)說(shuō)明下同步的原理王浴,只有搞明白了同步的原理脆炎,才能理解為啥在下線一臺(tái)后,同步就會(huì)延遲叼耙。
集群同步架構(gòu)
nacos 注冊(cè)中心是最終一致性腕窥,2.x的版本和客戶端是通過(guò)grpc長(zhǎng)鏈接來(lái)實(shí)現(xiàn)的,所以集群集群的每個(gè)節(jié)點(diǎn)會(huì)把注冊(cè)到自己節(jié)點(diǎn)的client筛婉,同步給集群其他幾臺(tái)節(jié)點(diǎn)簇爆,具體的架構(gòu)圖如下:
-
client :就是provdier和nacos 建立鏈接后 nacos維護(hù)的一個(gè)記錄,用來(lái)標(biāo)記一個(gè)客戶端爽撒,分兩種:
- native client:即直接鏈接在本節(jié)點(diǎn)的provider入蛆,也該節(jié)點(diǎn)是client的責(zé)任節(jié)點(diǎn)。
- 非native client硕勿,即是集群其他節(jié)點(diǎn)同步過(guò)來(lái)的哨毁,也就是非責(zé)任節(jié)點(diǎn),
Verify 任務(wù)
同步給其他節(jié)點(diǎn)源武,怎么知道該client是否活的呢扼褪,因?yàn)殒溄佑植皇呛退⒌南牖茫瑸榱私鉀Q這個(gè)問(wèn)題,nacos 會(huì)為所有要同步的節(jié)點(diǎn)话浇,生成一個(gè)verify 任務(wù)脏毯,去更新下同步過(guò)去的client的活躍時(shí)間,來(lái)讓集群其他節(jié)點(diǎn)幔崖,認(rèn)為是活的食店,不要給刪了,nacos會(huì)有一個(gè)定時(shí)執(zhí)行的超時(shí)檢查任務(wù)赏寇,如果在3分鐘內(nèi)這個(gè)client 沒(méi)有更新活躍時(shí)間吉嫩,就認(rèn)為該同步過(guò)來(lái)的節(jié)點(diǎn)超時(shí)了,要干掉了Delete 任務(wù)
正常情況下嗅定,通過(guò)verify 任務(wù)來(lái)續(xù)約自娩,如果nacos發(fā)現(xiàn)這個(gè)client鏈接端了,異常了露戒,比如pod重啟了椒功,nacos 就會(huì)通過(guò)delete task 來(lái)告訴其他節(jié)點(diǎn)捶箱,該client已經(jīng)掛了智什,不需要維護(hù)了。
超時(shí)機(jī)制
上面介紹verify 任務(wù)時(shí)丁屎,說(shuō)了超時(shí)檢查荠锭,為了更好的理解,再提供一張圖:
同步原理
通過(guò)上面同步晨川,verify证九,delete 任務(wù),nacos讓provider在集群里保持一致共虑,做到了服務(wù)注冊(cè)無(wú)論鏈接到那個(gè)nacos節(jié)點(diǎn)愧怜,consumer 鏈接誰(shuí),都能消費(fèi)到全量的數(shù)據(jù)妈拌,就是這個(gè)同步來(lái)保證的拥坛。
上面只是一個(gè)概括同步機(jī)制,那同步是怎么實(shí)現(xiàn)的呢尘分,寫(xiě)再多猜惋,不如一張圖好理解,真正實(shí)現(xiàn)同步的原理在下面:
知識(shí)點(diǎn):
- 集群又幾個(gè)節(jié)點(diǎn)培愁,就同步幾次著摔,每次都生成一個(gè)同步任務(wù)。
- 當(dāng)前同步是添加到任務(wù)集合ConcurrentMap就結(jié)束定续,異步化谍咆。
- 有一個(gè)線程專門(mén)負(fù)責(zé)從ConcurrentMap取任務(wù)禾锤,因?yàn)槿蝿?wù)支持延遲,就是在這里判斷的摹察,超過(guò)延遲時(shí)間时肿,就刪除,同時(shí)生成一個(gè)新的提交到同步線程引擎隊(duì)列港粱。
- 同步引擎線程池螃成,線程數(shù)和cpu核數(shù)保持一致,每個(gè)線程匹配一個(gè)大小為2的15次方的隊(duì)列查坪,執(zhí)行線程就不斷的從這個(gè)隊(duì)列取任務(wù)來(lái)發(fā)給目前節(jié)點(diǎn)寸宏。
- 網(wǎng)絡(luò)發(fā)送是通過(guò)grpc異步發(fā)送。
- 失敗處理機(jī)制偿曙,因?yàn)椴荒軄G任務(wù)氮凝,所以失敗了是要把這個(gè)任務(wù)重新加回到隊(duì)列。
根因分析
通過(guò)這個(gè)圖望忆,特地標(biāo)記紅色的地方罩阵,就是我們這次停了一臺(tái)機(jī)器導(dǎo)致服務(wù)調(diào)用失敗的根因,我們用的是2.0.3的版本启摄,我們集群3臺(tái)機(jī)器稿壁,所以停了一臺(tái)時(shí),這臺(tái)機(jī)器上的鏈接全部斷開(kāi)歉备,客戶端會(huì)重連傅是,這時(shí)就產(chǎn)生了很多的同步任務(wù),要同步蕾羊,好了這么多任務(wù)要同步喧笔,但是一個(gè)任務(wù)要同步給兩臺(tái)機(jī)器,也就是兩個(gè)任務(wù)龟再,同步給這個(gè)故障的節(jié)點(diǎn)由于在前面沒(méi)有判斷书闸,到最后執(zhí)行同步的線程檢查的時(shí)候,肯定是失敗的利凑,就等100ms浆劲,還重試3次,也就是往這臺(tái)故障的節(jié)點(diǎn)同步一個(gè)任務(wù)截碴,這個(gè)同步線程要阻塞300ms梳侨,才能去處理下一個(gè)任務(wù),這個(gè)地方的代碼貼下日丹,更直觀點(diǎn):
所以在停掉一臺(tái)節(jié)點(diǎn)的情況走哺,一個(gè)任務(wù)要延遲300ms,哪超過(guò)600個(gè)任務(wù)要同步時(shí)哲虾,這地600以后的任務(wù)同步延遲就在3分鐘以上丙躏,就超過(guò)了前面我們說(shuō)的同步client的超時(shí)時(shí)間择示,就觸發(fā)了刪除操作,push了錯(cuò)誤的數(shù)據(jù)給客戶端晒旅。
所以如果你是2.0.3的版本栅盲,平時(shí)重啟時(shí)一定要快,如果要長(zhǎng)時(shí)間下線的废恋,一定要升級(jí)到新的版本谈秫,否則給你的大禮就是故障了。
解決方案
上面的問(wèn)題鱼鼓, 是因?yàn)橥窖舆t導(dǎo)致超時(shí)拟烫,被刪除,觸發(fā)推送迄本,但是服務(wù)本身是沒(méi)有任何問(wèn)題的硕淑,而且除了這個(gè)場(chǎng)景外,還會(huì)有其他的問(wèn)題導(dǎo)致錯(cuò)誤推送嘉赎,比如nacos 節(jié)點(diǎn)cpu100%置媳,fugc等,網(wǎng)絡(luò)問(wèn)題等公条,都會(huì)觸發(fā)該問(wèn)題拇囊,所以我們需要一個(gè)完整的解決方案,確保nacos 無(wú)論出現(xiàn)了啥問(wèn)提赃份,都不能影響服務(wù)的調(diào)用寂拆,服務(wù)都是正常的,你nacos 有問(wèn)題,為啥影響服務(wù)調(diào)用忙干。
所以我們從兩個(gè)方便入手窍蓝,一個(gè)是增加一個(gè)反向檢查,確保服務(wù)正常時(shí)串结,不再亂推,還有一個(gè)就時(shí)推拉空保護(hù)。兩個(gè)措施結(jié)合英上,能把nacos穩(wěn)住。
反向檢查
在超時(shí)任務(wù)觸發(fā)時(shí)啤覆,如果超時(shí)了苍日,就對(duì)client做一次健康檢查,通過(guò)類似tcp telnet機(jī)制窗声,因?yàn)槲覀冇蟹?wù)的ip和端口相恃,通過(guò)建立鏈接來(lái)判斷是否成功,現(xiàn)在服務(wù)注冊(cè)的都有這個(gè)機(jī)制笨觅,比如consul都支持拦耐,不過(guò)這里需要注意的事耕腾,檢查通過(guò)了,需要做一些臟數(shù)據(jù)清理杀糯。
推拉空保護(hù)
客戶端從nacos獲取數(shù)據(jù)扫俺,就兩個(gè)地方,一個(gè)是客戶端主動(dòng)拉固翰,一個(gè)是nacos推送狼纬,所以在這兩個(gè)地方都要加一個(gè)保護(hù),防止推送空的實(shí)例骂际,我們是定義一個(gè)空的發(fā)展畸颅,計(jì)算實(shí)例數(shù)小于該閥值時(shí),就不推送或者飯后空的集合方援。