dubbo微服務之間流水號的隱式傳遞

做開發的人都知道流水號這個概念,有業務流水號,交易流水號,請求流水號等等,各種流水號。

無論是啥名字的流水號,目的都是爲了在某個維度,讓一系列動作有一個唯一的標識。後面方便查日誌,查問題。系統間交互可以防止扯皮。

比如交易流水號,唯一標識一筆交易,這邊所說的交易可以是無業務含義的請求,也可以是賬務交易。如果是標識無業務含義的請求。一般會在交易開始時生成一個32位或者64位的唯一編碼。這個編碼會在交易的縱向流程中傳遞,可以用參數傳遞,也可以放到上下文 Context 中,還可以放到 ThreadLocal 中。

在單體應用 Singleton Application 中,對流水號的處理比較簡單,無論是參數傳遞還是 ThreadLocal 都沒有什麼難度。

在微服務環境下,一筆交易會經過多次微服務之間的交互,並且需要把流水號傳遞下去,它的傳遞鏈路可能是這樣的(服務A) --> (服務B --> )(服務C) --> (服務D) -->(服務E),使用 dubbo 作爲 RPC 框架。有2種傳遞流水號的方式(當然這只是我自己覺得,可能還有更好的處理方式),下面詳細說說。

參數傳遞法

這種方式很容易理解,就是把流水號作爲接口的一個參數 (String jnl) 。這種方式結構簡單,實現起來也很簡單。但是有諸多問題。

1.幾乎所有暴露的接口服務都會有這麼個參數。不好看。

2.交易流水號,實際與業務本身相關性比較低。微服務之間的參數應該儘量與業務強相關,而不是佔用單獨的參數位置來傳遞與業務無關的東西。

3.這樣的方式,維護起來麻煩,寫啥服務都得加這麼個參數,移植性也很差。

我個人不喜歡這樣的做法。

利用dubbo的Attachment做隱式傳遞

還是模擬剛剛的傳遞過程,把一個32位的Jnl在服務間傳遞。(服務A) --> (服務B --> )(服務C)

1.A服務是入口,生成32位流水號 Jnl

2.A服務把Jnl放到 ThreadLocal 中

3.在調用B系統之前,把 Jnl 從 ThreadLocal 取出並且放入 Invocation Attachment 中(這個過程由 Dubbo Filter 做)

4.B系統接收 Invocation 信息,並且反序列化。從 Attacment 中取出 Jnl 放入 ThreadLocal中(這個過程由 Dubbo Filter 做)

5.B系統調用C系統時重複剛剛的幾步操作。

簡單理解:在運行過程中, Jnl 始終從一個系統的 ThreadLocal 轉移到另一個系統的 ThreadLocal 中。轉移過程通過取和放都通過 Dubbo Filter 來完成。

這個過程涉及到3個角色。

① ThreadLocal

② Consumer 一方的 Filter ,作用是讀取 ThreadLocal 中的Jnl放入 Invocation Attachment中

③ Privoder 一方 Filter ,作用是讀取 Invocation Attachment 中的Jnl放入 ThreadLocal 中

下面看代碼實現

<pre language="typescript" code_block="true">public class AccessJnlUtil {

   // 獲取
   public static String getAccessJnl() {
      return (String) RpcContext.getContext().get("AccessJnl");
   }

   // 放入
   public static void setAccessJnl(String accessJnl) {
      RpcContext.getContext().set("AccessJnl", accessJnl);
   }

   public static void deleteAccessJnl() {
      RpcContext.getContext().remove("AccessJnl");
   }
}
複製代碼</pre>

AccessJnlUtil 是用來對AccessJnl做put和set操作的。利用了 Dubbo RpcContext , RpcContext 本身就是 ThreadLocal 的封裝,不熟悉的話可以追蹤下源碼,這邊不再贅述。

<pre language="typescript" code_block="true">public class AccessJnlConsumerFilter implements Filter {

	@Override
	public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
		invocation.getAttachments().put("AccessJnl", AccessJnlUtil.getAccessJnl());
		return invoker.invoke(invocation);
	}
}


AccessJnlConsumerFilter 是消費方的 Filter ,在實際調用 invoke 之前,把 AccessJnl 從 ThreadLocal 中取出,放到 Invocation Attachment 中。

<pre language="typescript" code_block="true">public class AccessJnlProviderFilter implements Filter {

   @Override
   public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
      AccessJnlUtil.setAccessJnl(RpcContext.getContext().getAttachment("AccessJnl"));
      return invoker.invoke(invocation);
   }
}

AccessJnlProviderFilter 是消費方的 Filter ,在實際調用 invoke 之前,把 AccessJnl 從 Invocation Attachment 中取出,放到 ThreadLocal 中。

然後就是把兩個 Filter 配置到系統中,具體配置方式可以參考 Dubbo文檔

總結

利用 dubbo 的 Attachment 做隱式傳遞。我個人比較喜歡這樣的處理方式。相對優雅。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章