瓜子二手车在 Dubbo 版本升级、多机房方案方面的思考和实践
前言
隨著瓜子業(yè)務(wù)的不斷發(fā)展,系統(tǒng)規(guī)模在逐漸擴大,目前在瓜子的私有云上已經(jīng)運行著數(shù)百個 Dubbo 應(yīng)用,上千個 Dubbo 實例。瓜子各部門業(yè)務(wù)迅速發(fā)展,版本沒有來得及統(tǒng)一,各個部門都有自己的用法。隨著第二機房的建設(shè),Dubbo 版本統(tǒng)一的需求變得越發(fā)迫切。幾個月前,公司發(fā)生了一次與 Dubbo 相關(guān)的生產(chǎn)事故,成為了公司 基于社區(qū) Dubbo 2.7.3 版本升級的誘因。
接下來,我會從這次線上事故開始,講講我們這段時間所做的 Dubbo 版本升級的歷程以及我們規(guī)劃的 Dubbo 后續(xù)多機房的方案。
一、Ephermal節(jié)點未及時刪除導(dǎo)致provider不能恢復(fù)注冊的問題修復(fù)
事故背景
在生產(chǎn)環(huán)境,瓜子內(nèi)部各業(yè)務(wù)線共用一套zookeeper集群作為dubbo的注冊中心。2019年9月份,機房的一臺交換機發(fā)生故障,導(dǎo)致zookeeper集群出現(xiàn)了幾分鐘的網(wǎng)絡(luò)波動。在zookeeper集群恢復(fù)后,正常情況下dubbo的provider應(yīng)該會很快重新注冊到zookeeper上,但有一小部分的provider很長一段時間沒有重新注冊到zookeeper上,直到手動重啟應(yīng)用后才恢復(fù)注冊。
排查過程
首先,我們統(tǒng)計了出現(xiàn)這種現(xiàn)象的dubbo服務(wù)的版本分布情況,發(fā)現(xiàn)在大多數(shù)的dubbo版本中都存在這種問題,且發(fā)生問題的服務(wù)比例相對較低,在github中我們也未找到相關(guān)問題的issues。因此,推斷這是一個尚未修復(fù)的且在網(wǎng)絡(luò)波動情況的場景下偶現(xiàn)的問題。
接著,我們便將出現(xiàn)問題的應(yīng)用日志、zookeeper日志與dubbo代碼邏輯進行相互印證。在應(yīng)用日志中,應(yīng)用重連zookeeper成功后provider立刻進行了重新注冊,之后便沒有任何日志打印。而在zookeeper日志中,注冊節(jié)點被刪除后,并沒有重新創(chuàng)建注冊節(jié)點。對應(yīng)到dubbo的代碼中,只有在FailbackRegistry.register(url)的doRegister(url)執(zhí)行成功或線程被掛起的情況下,才能與日志中的情況相吻合。
public void register(URL url) {super.register(url);failedRegistered.remove(url);failedUnregistered.remove(url);try {// Sending a registration request to the server sidedoRegister(url);} catch (Exception e) {Throwable t = e;// If the startup detection is opened, the Exception is thrown directly.boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)&& url.getParameter(Constants.CHECK_KEY, true)&& !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());boolean skipFailback = t instanceof SkipFailbackWrapperException;if (check || skipFailback) {if (skipFailback) {t = t.getCause();}throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);} else {logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);}// Record a failed registration request to a failed list, retry regularlyfailedRegistered.add(url);}}在繼續(xù)排查問題前,我們先普及下這些概念:dubbo默認(rèn)使用curator作為zookeeper的客戶端,curator與zookeeper是通過session維持連接的。當(dāng)curator重連zookeeper時,若session未過期,則繼續(xù)使用原session進行連接;若session已過期,則創(chuàng)建新session重新連接。而ephemeral節(jié)點與session是綁定的關(guān)系,在session過期后,會刪除此session下的ephemeral節(jié)點。
繼續(xù)對doRegister(url)的代碼進行進一步排查,我們發(fā)現(xiàn)在CuratorZookeeperClient.createEphemeral(path)方法中有這么一段邏輯:在createEphemeral(path)捕獲了NodeExistsException,創(chuàng)建ephemeral節(jié)點時,若此節(jié)點已存在,則認(rèn)為ephemeral節(jié)點創(chuàng)建成功。這段邏輯初看起來并沒有什么問題,且在以下兩種常見的場景下表現(xiàn)正常:
但是實際上還有一種極端場景,zookeeper的Session過期與刪除Ephemeral節(jié)點不是原子性的,也就是說客戶端在得到Session過期的消息時,Session對應(yīng)的Ephemeral節(jié)點可能還未被zookeeper刪除。此時dubbo去創(chuàng)建Ephemeral節(jié)點,發(fā)現(xiàn)原節(jié)點仍存在,故不重新創(chuàng)建。待Ephemeral節(jié)點被zookeeper刪除后,便會出現(xiàn)dubbo認(rèn)為重新注冊成功,但實際未成功的情況,也就是我們在生產(chǎn)環(huán)境遇到的問題。
此時,問題的根源已被定位。定位問題之后,經(jīng)我們與 Dubbo 社區(qū)交流,發(fā)現(xiàn)考拉的同學(xué)也遇到過同樣的問題,更確定了這個原因。
問題的復(fù)現(xiàn)與修復(fù)
定位到問題之后,我們便開始嘗試本地復(fù)現(xiàn)。由于zookeeper的Session過期但Ephemeral節(jié)點未被刪除的場景直接模擬比較困難,我們通過修改zookeeper源碼,在Session過期與刪除Ephemeral節(jié)點的邏輯中增加了一段休眠時間,間接模擬出這種極端場景,并在本地復(fù)現(xiàn)了此問題。
在排查問題的過程中,我們發(fā)現(xiàn)kafka的舊版本在使用zookeeper時也遇到過類似的問題,并參考kafka關(guān)于此問題的修復(fù)方案,確定了dubbo的修復(fù)方案。在創(chuàng)建Ephemeral節(jié)點捕獲到NodeExistsException時進行判斷,若Ephemeral節(jié)點的SessionId與當(dāng)前客戶端的SessionId不同,則刪除并重建Ephemeral節(jié)點。在內(nèi)部修復(fù)并驗證通過后,我們向社區(qū)提交了issues及pr。
kafka類似問題issues:https://issues.apache.org/jira/browse/KAFKA-1387
dubbo注冊恢復(fù)問題issues:https://github.com/apache/dubbo/issues/5125
二、瓜子的dubbo升級歷程
上文中的問題修復(fù)方案已經(jīng)確定,但我們顯然不可能在每一個dubbo版本上都進行修復(fù)。在咨詢了社區(qū)dubbo的推薦版本后,我們決定在dubbo2.7.3版本的基礎(chǔ)上,開發(fā)內(nèi)部版本修復(fù)來這個問題。并借這個機會,開始推動公司dubbo版本的統(tǒng)一升級工作。
為什么要統(tǒng)一dubbo版本
為什么選擇dubbo2.7.3
內(nèi)部版本定位
基于社區(qū)dubbo2.7.3版本開發(fā)的dubbo內(nèi)部版本屬于過渡性質(zhì)的版本,目的是為了修復(fù)線上provider不能恢復(fù)注冊的問題,以及一些社區(qū)dubbo2.7.3的兼容性問題。瓜子的dubbo最終還是要跟隨社區(qū)的版本,而不是開發(fā)自已的內(nèi)部功能。因此我們在dubbo內(nèi)部版本中修復(fù)的所有問題均與社區(qū)保持了同步,以保證后續(xù)可以兼容升級到社區(qū)dubbo的更高版本。
兼容性驗證與升級過程
我們在向dubbo社區(qū)的同學(xué)咨詢了版本升級方面的相關(guān)經(jīng)驗后,于9月下旬開始了dubbo版本的升級工作。
首先,我們梳理了一些需要驗證的兼容性case,針對公司內(nèi)部使用較多的dubbo版本,與dubbo2.7.3一一進行了兼容性驗證。經(jīng)驗證,除dubboX外,dubbo2.7.3與其他dubbo版本均兼容。dubboX由于對dubbo協(xié)議進行了更改,與dubbo2.7.3不兼容。
在初步驗證兼容性通過后,我們與業(yè)務(wù)線合作,挑選了一些重要程度較低的項目,在生產(chǎn)環(huán)境對dubbo2.7.3與其他版本的兼容性進行了進一步驗證。并在內(nèi)部版本修復(fù)了一些兼容性問題。
在10月初,完成了dubbo兼容性驗證后,我們開始在各個業(yè)務(wù)線推動dubbo的升級工作。截止到12月初,已經(jīng)有30%的dubbo服務(wù)的完成了版本升級。按照排期,預(yù)計于2020年3月底前完成公司dubbo版本的統(tǒng)一升級。
兼容性問題匯總
在推動升級dubbo2.7.3版本的過程整體上比較順利,當(dāng)然也遇到了一些兼容性問題:
-
創(chuàng)建zookeeper節(jié)點時提示沒有權(quán)限
dubbo配置文件中已經(jīng)配置了zookeeper的用戶名密碼,但在創(chuàng)建zookeeper節(jié)點時卻拋出KeeperErrorCode = NoAuth的異常,這種情況分別對應(yīng)兩個兼容性問題:- issues:https://github.com/apache/dubbo/issues/5076
dubbo在未配置配置中心時,默認(rèn)使用注冊中心作為配置中心。通過注冊中心的配置信息初始化配置中心配置時,由于遺漏了用戶名密碼,導(dǎo)致此問題。 - issues:https://github.com/apache/dubbo/issues/4991
dubbo在建立與zookeeper的連接時會根據(jù)zookeeper的address復(fù)用之前已建立的連接。當(dāng)多個注冊中心使用同一個address,但權(quán)限不同時,就會出現(xiàn)NoAuth的問題。 - 參考社區(qū)的pr,我們在內(nèi)部版本進行了修復(fù)。
- issues:https://github.com/apache/dubbo/issues/5076
-
curator版本兼容性問題
- dubbo2.7.3與低版本的curator不兼容,因此我們默認(rèn)將curator版本升級至4.2.0
- 分布式調(diào)度框架elastic-job-lite強依賴低版本的curator,與dubbo2.7.3使用的curator版本不兼容,這給dubbo版本升級工作帶來了一定阻塞。考慮到elastic-job-lite已經(jīng)很久沒有人進行維護,目前一些業(yè)務(wù)線計劃將elastic-job-lite替換為其他的調(diào)度框架。
- openFeign與dubbo兼容性問題
issues:?https://github.com/apache/dubbo/issues/3990
dubbo的ServiceBean監(jiān)聽spring的ContextRefreshedEvent,進行服務(wù)暴露。openFeign提前觸發(fā)了ContextRefreshedEvent,此時ServiceBean還未完成初始化,于是就導(dǎo)致了應(yīng)用啟動異常。
參考社區(qū)的pr,我們在內(nèi)部版本修復(fù)了此問題。
- RpcException兼容性問題
dubbo低版本consumer不能識別dubbo2.7版本provider拋出的org.apache.dubbo.rpc.RpcException。因此,在consumer全部升級到2.7之前,不建議將provider的com.alibaba.dubbo.rpc.RpcException改為org.apache.dubbo.rpc.RpcException - qos端口占用
dubbo2.7.3默認(rèn)開啟qos功能,導(dǎo)致一些混部在物理機的dubbo服務(wù)升級時出現(xiàn)qos端口占用問題。關(guān)閉qos功能后恢復(fù)。 - 自定義擴展兼容性問題
業(yè)務(wù)線對于dubbo的自定義擴展比較少,因此在自定義擴展的兼容性方面暫時還沒有遇到比較難處理的問題,基本上都是變更package導(dǎo)致的問題,由業(yè)務(wù)線自行修復(fù)。 - skywalking agent兼容性問題
我們項目中一般使用skywalking進行鏈路追蹤,由于skywalking agent6.0的plugin不支持dubbo2.7,因此統(tǒng)一升級skywalking agent到6.1。
三、dubbo多機房方案
瓜子目前正在進行第二機房的建設(shè)工作,dubbo多機房是第二機房建設(shè)中比較重要的一個話題。在dubbo版本統(tǒng)一的前提下,我們就能夠更順利的開展dubbo多機房相關(guān)的調(diào)研與開發(fā)工作。
初步方案
我們咨詢了dubbo社區(qū)的建議,并結(jié)合瓜子云平臺的現(xiàn)狀,初步確定了dubbo多機房的方案。
同機房優(yōu)先調(diào)用
dubbo同機房優(yōu)先調(diào)用的實現(xiàn)比較簡單,相關(guān)邏輯如下:
針對以上邏輯,我們簡單實現(xiàn)了dubbo通過環(huán)境變量進行路由的功能,并向社區(qū)提交了pr。
dubbo通過環(huán)境變量路由pr:?https://github.com/apache/dubbo/pull/5348
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的瓜子二手车在 Dubbo 版本升级、多机房方案方面的思考和实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eBay邓明:dubbo-go 中 me
- 下一篇: 战“疫”期,阿里云云效团队在家高效开发实