新聞中心
注冊中心zookeeper重啟恢復(fù)后,線上微服務(wù)卻全部掉線了,怎么回事?!

邢臺縣網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)2013年開創(chuàng)至今到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
最近因為一次錯誤的運維操作,導(dǎo)致線上注冊中心zk被重啟。而zk重啟后發(fā)現(xiàn)所有線上微服務(wù)開始不斷掉線,造成了持續(xù)30分鐘的P0?故障。
整體排查過程深入學(xué)習(xí)了 zookeeper的session機制,以及在這種異常情況下,RPC框架應(yīng)該如何處理。
好了,一起來回顧下這次線上故障吧,最佳實踐總結(jié)放在最后,千萬不要錯過。
1、現(xiàn)象描述
某天晚上19:43分左右,誤操作將線上zk集群下線(stop),總共7臺節(jié)點,下線了6臺,導(dǎo)致zk停止工作。
在發(fā)現(xiàn)節(jié)點下掉后,于19:51分左右將所有zk節(jié)點進行重啟(start),期間服務(wù)正常運行,沒有收到批量業(yè)務(wù)調(diào)用的報錯和客訴。
直到19:56分,開始收到大面積調(diào)用失敗的警報和客訴,我們嘗試著依賴自研RPC框架與zk間重連后的「自動恢復(fù)」機制,希望能夠在短時間內(nèi)批量恢復(fù)。
但是很不幸,過了接近8分鐘,沒有任何大面積恢復(fù)的跡象。
結(jié)合zk znode節(jié)點數(shù)上升非常緩慢的情況,于是我們采取了應(yīng)急措施,將所有微服務(wù)的pod原地重啟,執(zhí)行重啟后效果顯著,大面積服務(wù)在短時間內(nèi)逐步恢復(fù)。
2、初步分析
我們自研的RPC框架采用典型的 注冊中心+provider+consumer 的模式,通過zk臨時節(jié)點的方式做服務(wù)的注冊發(fā)現(xiàn),如下圖所示。
結(jié)合故障期間發(fā)生的現(xiàn)象,我們初步分析:
- 階段1:zk集群停服(stop)期間,業(yè)務(wù)能夠正常調(diào)用。原因是consumer無法訪問zk,暫時失去服務(wù)發(fā)現(xiàn)能力,所以在這個期間只要服務(wù)沒有重啟,就不會刷新本地的服務(wù)發(fā)現(xiàn)provider緩存列表provider-list,調(diào)用無異常。
- 階段2:zk集群啟動完畢后,服務(wù)間立刻出現(xiàn)調(diào)用問題。原因是consumer連接上zk后,立刻進行服務(wù)發(fā)現(xiàn)操作,然而provider服務(wù)這時還沒重新注冊到zk,讀取到的是空地址列表,造成了業(yè)務(wù)的批量報錯。
- 階段3:zk恢復(fù)后續(xù)一段時間,provider服務(wù)仍然沒「自動重連」到zk,導(dǎo)致consumer持續(xù)報錯。在所有服務(wù)全量重啟后,provider服務(wù)重新注冊成功,consumer恢復(fù)。
這里存在一個問題:
為什么zk集群恢復(fù)后,provider客戶端「自動重連」注冊中心的機制沒有生效?導(dǎo)致consumer被推送了空地址列表后,沒有再收到重新的provider注冊節(jié)點信息了。
3、深入排查
(1問題復(fù)現(xiàn)
根據(jù)大量測試,我們找到了穩(wěn)定復(fù)現(xiàn)本次問題的方法:
zk session過期包括 「服務(wù)端過期」 和 「客戶端過期」,在「客戶端過期」情況下恢復(fù)zk集群,會導(dǎo)致「臨時節(jié)點」丟失,且無法自動恢復(fù)的情況。
(2原因分析
1)?在集群重啟恢復(fù)后,RPC框架客戶端立刻就與zk集群取得重連,將保存在本地內(nèi)存待注冊的providers節(jié)點 + 待訂閱的consumers節(jié)點 進行重建。
2)但是zk集群此時根據(jù)snapshot恢復(fù)的「臨時節(jié)點」(包括provider和consumer) 都還在,因此重建操作返回NodeExist異常,重建失敗了。(問題1:為什么沒有重試?)
3)在集群重啟恢復(fù)40s后,將過期Session相關(guān)的 臨時節(jié)點全都移除了。(問題2:為什么要移除?)
4)consumer監(jiān)聽到 節(jié)點移除 的空列表,清空了本地provider列表。故障發(fā)生了。?
基于這個分析,我們需要進一步圍繞2個問題進行源碼的定位:
- 問題1:zk集群恢復(fù)后,前40s,為什么RPC框架的客戶端在創(chuàng)建臨時節(jié)點失敗后沒有重試?
- 問題2:zk集群恢復(fù)后,40s后,為什么zk會刪除之前所有已經(jīng)恢復(fù)的臨時節(jié)點?
(3)問題1:為什么臨時節(jié)點創(chuàng)建失敗沒有重試?
通過源碼分析,我們看到,RPC框架客戶端與服務(wù)端取得重連后,會將內(nèi)存里老的臨時節(jié)點進行重新創(chuàng)建。
這段邏輯看來沒有什么問題,doRegister成功之后才會將該節(jié)點從失敗列表中移除,否則將繼續(xù)定時去重試創(chuàng)建。
繼續(xù)往下走,關(guān)鍵點來了:
這里我們可以看到,在創(chuàng)建臨時節(jié)點時,吞掉了服務(wù)端返回的NodeExistsException,使整個外層的doRegister和doSubscribe(訂閱)方法在這種情況下都被認為是重新創(chuàng)建成功,所以只創(chuàng)建了一次。
正如上面分析的,其實正常情況下,這里對NodeExistsException不做處理是沒有問題的,就是節(jié)點已經(jīng)存在不用再添加了,也不需要再重試了,但是伴隨服務(wù)端后續(xù)踢出老sessionId同時刪除了相關(guān)臨時節(jié)點,就引起了故障。
(4)問題2:zk為什么刪除已經(jīng)恢復(fù)的臨時節(jié)點?
1)從zk的session機制說起
眾所周知,zk session管理在客戶端、服務(wù)端都有實現(xiàn),并且兩者通過心跳進行交互。
在發(fā)送心跳包時,客戶端會攜帶自己的sessionId,服務(wù)端收到請求,檢查sessionId確認存活后再發(fā)送返回結(jié)果給客戶端。
如果客戶端發(fā)送了一個服務(wù)端并不知道的sessionId,那么服務(wù)端會生成一個新的sessionId頒布給客戶端,客戶端收到后本地進行sessionid的刷新。
2)zk客戶端(curator)session過期機制
當客戶端(curator)本地sessionTimeout超時時,會進行本地zk對象的重建(reset),我們從源碼可以看到默認將本地的sessionId重置為0了。
zk服務(wù)端后續(xù)收到這個為“0”sessionId,認為是一個未知的session需要創(chuàng)建,接著就為客戶端創(chuàng)建了一個新的sessionId。
3) 服務(wù)端(zookeeper)session過期處理機制
服務(wù)端(zookeeper) sessionTimeout的管理,是在zk會話管理器中看到一個線程任務(wù),不斷判斷管理的session是否有超時(獲取下一個過期時間點nextExpirationTime已經(jīng)超時的會話),并進行會話的清理。
我們繼續(xù)往下走,關(guān)鍵點來了,在清理session的過程中,除了將sessionId從本地expiryMap中清除外,還進行了臨時節(jié)點的清理:
?原來zkserver端是將sessionId和它所創(chuàng)建的臨時節(jié)點進行了綁定。伴隨著服務(wù)端sessionId的過期,綁定的所有臨時節(jié)點也會隨之刪除。
因此,zk集群恢復(fù)后40s,zk服務(wù)端session超時,刪除了過期session的所有相關(guān)臨時節(jié)點。?
4、故障根本原因總結(jié)
1)zk集群恢復(fù)的第一時間,對zk的snapshot文件進行了讀取并初始化zk數(shù)據(jù),取到了老session,進行了create session的操作,完成了一次老session的續(xù)約(重置40s)。
集群恢復(fù)關(guān)鍵入口-重新加載snapshot:
進行session恢復(fù)(創(chuàng)建)操作,默認session timeout 40s:?
?2)而此時客戶端session早已經(jīng)過期,帶著空sessionid 0x0進行重連,獲得新sessionId。但是此時RPC框架在臨時節(jié)點注冊失敗后吞掉了服務(wù)端返回的NodeExistsException,被認為是重新創(chuàng)建成功,所以只創(chuàng)建了一次。
3)zk集群恢復(fù)后經(jīng)過40s最終因為服務(wù)端session過期,將過期sessionId和及其綁定的臨時節(jié)點進行了清除。
4)consumer監(jiān)聽到 節(jié)點移除 的空列表,清空了本地provider列表。故障發(fā)生了。?
5、解決方案
經(jīng)過上面的源碼分析,解決方案有兩種:
- 方案1:客戶端(curator)設(shè)置session過期時間更長或者不過期,那么集群恢復(fù)后的前40s,客戶端帶著原本的sessionid跟服務(wù)端做一次請求,就自動續(xù)約了,不再過期。
- 方案2:客戶端session過期后,帶著空sessionid 0x0進行重連的時候,對NodeExsitException做處理,進行 刪除-重添加 操作,保證重連成功。
于是我們調(diào)研了一下業(yè)界使用zk的開源微服務(wù)框架是否支持自愈,以及如何實現(xiàn)的:
dubbo采用了方案2。
注釋也寫得非常清楚:?
“ZNode路徑已經(jīng)存在,因為我們只會在會話過期時嘗試重新創(chuàng)建節(jié)點,所以這種重復(fù)可能是由zk服務(wù)器的刪除延遲引起的,這意味著舊的過期會話可能仍然保存著這個ZNode,而服務(wù)器只是沒有時間進行刪除。在這種情況下,我們可以嘗試刪除并再次創(chuàng)建?!?/p>
看來dubbo確實后續(xù)也考慮到這個邊界場景,防止踩坑。
所以最后我們的解決方案也是借鑒dubbo fix的邏輯,進行節(jié)點的替換:先deletePath再createPath,這么做的原因是將zk服務(wù)端內(nèi)存維護的過期sessionId替換新的sessionId,避免后續(xù)zk清理老sessionId時將所有綁定的節(jié)點刪除。
6、最佳實踐
回顧整個故障,我們其實還忽略了一點最佳實踐。
除了優(yōu)化對異常的捕獲處理外,RPC框架對注冊中心的空地址推送也應(yīng)該做特殊判斷,用業(yè)界的專業(yè)名詞來說,就是「推空保護」。
所謂「推空保護」,就是在服務(wù)發(fā)現(xiàn)監(jiān)聽獲取空節(jié)點列表時,維持本地服務(wù)發(fā)現(xiàn)列表緩存,而不是清空處理。
這樣可以完全避免類似問題。
網(wǎng)站欄目:Zookeeper恢復(fù)了,線上微服務(wù)卻全部掉線了,怎么回事?
網(wǎng)頁地址:http://www.fisionsoft.com.cn/article/djijcic.html


咨詢
建站咨詢
