採坑經驗:Dubbo 2.6.x版本中隱式參數attachment的錯誤使用方式,稍不注意就會出現生產事故

前言

  • 接觸dubbo分佈式框架的開發也有一段時間了,其中爲了解決項目中遇到的一些雜症,還特地學習了一下Dubbo服務暴露和服務引入的一些源碼知識點。最近在項目開發的過程當中,有使用到了dubbo的隱式參數技術點,但發現了幾個在使用上很是容易出錯而且一出錯就是生產事故的,如今記錄一下。

1、瞭解Dubbo隱式參數以前先了解下Dubbo的上下文信息

  • 什麼是Dubbo的上下文信息?這裏總結下本身的理解:java

    上下文中存放的是當前 調用過程中所需的環境信息。全部的配置信息都將轉換成URL的參數。RpcContext類就是Dubbo的上下文,可是它僅僅是一個ThreadLocal級別的 臨時狀態記錄器,當接收到RPC請求或發起RPC請求時,RpcContext的狀態都會變化。好比:A調用B、B再調用C的狀況下。 B機器的RpcContext會有以下的狀況發生:在B調用C以前,RpcContext記錄的是A調B的信息,在B調用C以後,RpcContext記錄的是B調C的信息。
  • 好比:咱們想要獲取到服務調用者的host相關信息,那麼咱們能夠在服務提供者中獲取當前消費此服務的消費者的host信息,其代碼以下所示:spring

    // 獲取調用方的host信息
    String serverIP = RpcContext.getContext().getRemoteHost();

2、Dubbo上下文攜帶的隱式參數attachment

  • 不知道各位在開發的時候,有沒有遇到一種須要額外傳遞給下游服務的參數(好比標識當前用戶請求的jwt、記錄分佈式系統全鏈路跟蹤的全局traceId)。當有這方面的需求時,咱們不可能修改方法的參數簽名,這與業務耦合了。此時,可能就須要使用Dubbo的隱式參數attachment了。什麼是attachment?能夠把它認定爲Dubbo協議中的一個擴展點。就有點相似於Http協議,咱們能夠自定義請求頭信息。而在Dubbo中,咱們也能夠自定義RPC請求中的參數。
  • 舉個例子:用戶在執行下單這個業務,最終確定會通過後臺的訂單服務、庫存服務等服務。如今有個需求:在訂單服務中,要明確知道這個訂單是哪一個用戶建立的。在庫存服務中,要明確知道這個商品最終是用戶的哪一個操做致使減小的。整個需求裏面有個核心:就是要知道操做者是誰!假設項目用的是jwt技術來記錄用戶的狀態,那麼訂單服務和庫存服務就必需要知道這個jwt字符串,將jwt解碼後,就能知道當前請求是由哪一個用戶發起的。在這樣的一個場景中,使用dubbo的隱式參數能夠達到上述的目的。實現的僞代碼以下所示:apache

    RpcContext.getContext().setAttachment("jwt", "xxxxxxxxxxjwt字符串xxxxxxxx");
    // dubbo rpc 調用庫存服務:減小庫存
    warehouseService.decrement();
    // dubbo rpc 調用訂單服務:建立訂單
    orderService.create();

這裏先總結下attachment在使用上的幾個特色:框架

一、key名稱不能以小駝峯命名,下游服務序列化後,會將key名稱變成全小寫(Dubbo 2.6.x版本,在2.7.x版本被修復了)分佈式

二、隱式參數設置後,僅在第一次RPC請求生效,後續的RPC請求將沒法獲取到隱式參數學習

由於attachment有上述的兩個特色,所以咱們很容易以下的兩個錯誤:測試

易犯錯誤1 易犯錯誤2
咱們在warehouseService.decrement()的下游服務中能順利的從attachment中獲取jwt參數,而在orderService.create()的下游服務中已經沒法順利的從attachment中獲取jwt參數了 在本例中,添加到attachment中的key爲jwt,是ok的。但若是咱們把key設置成大駝峯的命名方式,好比:userJwt。在通過Dubbo的一系列處理後,在warehouseService.decrement()下游服務中的rpcContext對象中的attachment中的key已經變成了userjwt,已經沒法獲取到key爲jwt的參數了。

3、篩選出最優的解決方案

  • 針對錯誤1,咱們有三個實現方案,其對應的方案策略以下所示:編碼

    方案 優勢 缺點
    方案1:在每一次發起RPC以前,都手動執行一次RpcContext.getContext().setAttachment("jwt", "xxxxxxxxxxjwt字符串xxxxxxxx");代碼 能解決問題,但不是最優方案 增長編碼的複雜度和代碼的重複度。
    方案2:使用spring的aop 的before機制,在執行rpc發起遠程服務以前,先把jwt放入到attachment中 能解決問題 dubbo的遠程調用對象自己就很重量,如今再添加一層代理,不利於定位問題。
    方案3:使用Dubbo的filter機制,在對指定遠程服務添加一層filter,filter的邏輯就是將jwt放入到attachment中去 比較好的一種解決方案,充分利用到了Dubbo框架自身提供的filter擴展。這也是比較通用的解決方案,全鏈路追蹤的traceId也是這麼玩的。(推薦 代碼閱讀性不高,filter同aop同樣,都是解耦的,不利於定位問題。
  • 針對錯誤2,在不對源碼進行擴展的狀況下,最簡單的方式就是修改key的命名方式,這裏可使用兩種方式:spa

    方式1 參考Dubbo源碼的org.apache.dubbo.common.constants.CommonConstants類中對添加到URL中的key的命令方式,多個單詞用.作區分
    方式2 單詞與單詞間使用自定義的符號作分隔,好比_,#等符號。這種方式也能夠區分於key是本身添加的仍是Dubbo框架自帶的。

4、總結

  • 避坑指南:有涉及到隱式參數的代碼改動時,必定要多測試。若某個環節被忽略,很容易形成生產事故
  • 若是你以爲個人文章有用的話,歡迎點贊和關注。
  • I'm a slow walker, but I never walk backwards
相關文章
相關標籤/搜索