那些年我們一起踩過的Dubbo坑

深夜裡的程式猿發表於2019-03-28

前言

微服務架構在如今的9102年已經不是什麼新鮮的話題了,但是怎麼做好微服務架構,卻又是一個永恆的話題。比如服務粒度的劃分,怎麼控制好粗細?服務劃分後,對於專案的部署會有什麼改變?... 這會是一個很大的話題,以後可以分開篇章探討一翻,但是我們本篇並不打算聊這個,而是討論一下具體的實現技術--dubbo。

dubbo歷史

2011 年末,阿里巴巴在 GitHub 上開源了基於 Java 的分散式服務治理框架 Dubbo,之後它成為了國內該類開源專案的佼佼者,許多開發者對其表示青睞。同時,先後有不少公司在實踐中基於 Dubbo 進行分散式系統架構,目前在 GitHub 上,它的 fork、star 數均已破萬。2014 年 10 月 30 號釋出版本 dubbo-2.4.11,修復了一個小 Bug,版本又陷入漫長的停滯到2017年九月份。

在dubbo停滯的期間呢,噹噹網 Fork 了阿里的一個 Dubbo 版本開始維護,並命名為 dubbox-2.8.0。值得注意的是,噹噹網擴充套件 Dubbo 服務框架支援 REST 風格遠端呼叫,並且跟隨著 ZooKeepe 和 Spring 升級了對應的版本。之後 Dubbox 一直在小版本維護,2015 年 3 月 31 號釋出了最後一個版本 dubbox-2.8.4。筆者公司用的也是這個版本,並稍微改造了下原始碼,下面會有提及。

其實在當前說到微服務,可能大家第一反應是springcloud,spring全家桶帶來的便捷是顯而易見的,然而為什麼我們這裡聊的是dubbo呢?原因之一是因為筆者公司只用了dubbo(別扔雞蛋....),其二呢其實rpc框架很多原理是相通的,當我們理解了其中一個,再去看其他的框架,會有一種似曾相識的感覺,最後也沒必要去爭論XX框架的好與壞,選擇最適合自己業務的就是最好的。

先交代下背景,我們這邊是從2016年開始使用dubbo,使用的是dubbox-2.8.4 版本,然後因為一些場景不合適改了下程式碼,重新打包成2.8.5提交至公司的私服使用。好了,接下來就開始進入正文,聊聊這幾年在dubbo使用過程中遇到坑,以及需要注意的地方吧。

正文

1、超時重試

這是一個很經典的坑,當時由於剛使用dubbo,很多配置都是基於預設的。剛好此時在專案中,有一個機器人送禮的邏輯比較複雜,當遇到某些特定的條件時,該邏輯的耗時會比正常情況下變長,這時候就出現了一個很神奇的現象,為何我只觸發了一次送禮的請求,而線上卻送了三次?

剛遇到這種情況可我驚呆了,重新審視了程式碼,發現並無問題。這就奇怪了,哪裡來的3次?後來掉了幾根頭髮以後,才在dubbo的文件中發現了服務這塊有timeout跟retry屬性,預設timeout=1000ms,retry=2。這下就豁然開朗,原來是第一次呼叫超時,導致又重試了2次,一共就是3次了。

找到問題的原因,我們就有辦法解決了。由於我們這個介面不是冪等性的,而且也不用返回什麼資訊給呼叫者,所以我們可以通過一個執行緒池來執行這段耗時的邏輯,讓rpc呼叫可以比較快的返回給呼叫者。這樣就不存在超時的問題了。或者可以配合增加timeout時間跟retry=0也能實現,具體的業務邏輯需要自己找到合適的解決方案。

2、dubbo使用內網ip

正常情況下,我們的服務呼叫推薦走內網連線的方式,效率是比較高的。但是有些特殊的情況,我們需要dubbo註冊服務的時候使用外網ip,該怎麼修改呢?這時候就需要修改我們的伺服器上 /etc/hosts 檔案了,新增一條 “外網ip 主機名”的記錄,restart我們的服務即可。

3、docker裡面註冊宿主機內網ip

說到微服務,當然也少不了docker了,我們當前用的是docker+overlay網路一個結構,直接把dubbo服務丟進容器裡面跑的話,註冊進zk的ip是容器ip。所以我們採取了一種折中的方式。

利用docker的特性,我們在建立容器的時候,把宿主機的ip以及需要暴露的埠寫進容器的環境變數裡面。然後就是修改dubbox的原始碼了,原始碼的com.alibaba.dubbo.registry.integration.RegistryProtocol類的getRegistedProviderUrl

方法,此方法用於返回註冊到註冊中心的URL。

private URL getRegistedProviderUrl(final Invoker<?> originInvoker){
        //targetUrl 註冊中心看到的地址
        URL targetUrl;
        URL providerUrl = getProviderUrl(originInvoker);
        //配置的容器環境變數
        String envParameterHost=System.getenv(ENV_HOST_KEY);
        String envParameterPort=System.getenv(ENV_PORT_KEY);
        if (StringUtils.isBlank(envParameterHost)||StringUtils.isBlank(envParameterPort)){//非容器環境:執行原來的註冊邏輯
            targetUrl=providerUrl.removeParameters(getFilteredKeys(providerUrl)).removeParameter(Constants.MONITOR_KEY);
        }else {//容器環境,如果環境變數中DOCKER_NAT_HOST和DOCKER_NAT_PORT兩個值都不為空則直接將這兩個值作為url註冊到zk
            //執行重新拼接url的操作,涉及敏感程式碼這裡不展示了
            targetUrl=dockerRegUrlWithHostAndPort;
        }
        return targetUrl;
    }
複製程式碼

4、未注意服務重名

其實這是我們開發人員粗心大意出現的情況,開發的時候註冊了2個相同簽名的服務,但是業務邏輯是完全不同的,這會導致一個之前執行的正常的業務會偶爾呼叫失敗,原因是因為dubbo的負載均衡策略,把一部分流量轉移到我們新註冊上來的服務上了,但是處理邏輯不同,導致錯誤。

5、版本的一致性

dubbo當前的releases版本已經去到2.7.1了,專案中要注意一下不同專案間版本的一致性,或者是dubbo跟dubbox的一些差別,最好做到統一,不然出現問題解決的成本會比較高。

6、屬性配置的優先順序

我們在dubbo的過程中會發現,提供者跟消費者中,很多屬性是一樣的,我們該怎麼配呢?在dubbo的文件當中其實有推薦的用法。

在提供者端儘量多提供消費者端的屬性。

參考文件,原因如下:

作服務的提供方,比服務消費方更清楚服務的效能引數,如呼叫的超時時間、合理的重試次數等

在 Provider 端配置後,Consumer 端不配置則會使用 Provider 端的配置,即 Provider 端的配置可以作為 Consumer 的預設值 。否則,Consumer 會使用 Consumer 端的全域性設定,這對於 Provider 是不可控的,並且往往是不合理的

Provider 端儘量多配置 Consumer 端的屬性,讓 Provider 的實現者一開始就思考 Provider 端的服務特點和服務質量等問題。

結語

其實在dubbo的使用過程中,還有挺多問題這裡沒列出來的,但是解決方法都差不多,首先文件要熟,做到心中有數,比如dubbo功能的成熟度,有些是不推薦線上上使用的,這時你就要謹慎了。然後文件裡面確實是有遺漏的問題,我們有必要可以debug dubbo的原始碼,這個過程會比較痛苦,但是對於排查問題跟個人能力的提高是有很有幫助的。

大家在dubbo的使用過程中有什麼問題也可以交流一下~

github:github.com/apache/incu…

中文使用手冊:dubbo.apache.org/zhcn/docs/u…

QQ圖片20190326125343.png

喜歡的話,關注一下微信公眾號《深夜裡的程式猿》,裡面更多精彩原創文章,還有小驚喜等著你哦~

相關文章