1. 程式人生 > 其它 >Dubbo-go 服務代理模型

Dubbo-go 服務代理模型

簡介:HSF 是阿里集團 RPC/服務治理 領域的標杆,Go 語言又因為其高併發,雲原生的特性,擁有廣闊的發展前景和實踐場景,服務代理模型只是一種落地場景,除此之外,還有更多的應用場景值得我們在研發的過程中去探索和總結。

作者 | 李志信

背景

Dubbo-go 生態包括 Dubbo-go v3.0 、v1.5、pixiu 等子專案,在可擴充套件性上提供了靈活的定製化方式。

眾所周知,HSF 是阿里集團 RPC/服務治理 領域的標杆框架。HSF-go 是 go 語言實現的 HSF 框架,由中介軟體團隊維護,由於 Go 語言的特性,在跨語言呼叫場景,雲原生元件整合服務代理場景扮演重要角色,目前擁有 Dapr Binding實現,並且在函式計算(FC)場景,跨雲場景,脫雲獨立部署場景產生價值,並在釘釘、Lazada、高德等技術團隊擁有落地場景。HSF-go 屬於 Dubbo-go 生態體系內的一環,是開源專案 Dubbo-go 的定製化實現。

縱觀 HSF-go 的一系列和服務代理相關的場景,我希望在這裡分享一下其作為服務代理的實踐與原理,歡迎和大家一起交流。

HSF-go 泛化呼叫模型

1、泛化呼叫

首先了解一下 Dubbo 的泛化呼叫,就是不依賴二方包的情況下,通過傳入方法名,方法簽名和引數值,就可以呼叫到下游服務。

而 Golang 的泛化呼叫和 Java 角度略有不同,這與語言特性有關。Go 不支援類繼承和方法過載,並且沒有二方包的概念。Java 的二方包可以抽象為一套由客戶端和服務端約定好的介面資訊,包含介面名、方法名、引數列表、具體引數定義,這些基礎概念在任何 RPC 場景都是必須的,只是表現形式不同:對 Java 來說就是二方包,對 gRPC 來說就是 proto 檔案以及編譯產物,對相容 Dubbo 協議的 Dubbo-go 來說,就是使用相容 Java 版本的 Hessian 序列化介面。當然使用 Go 編寫 Hessian 介面這種適配方式帶來了一些困擾,就是讓 Go 開發者寫起來比較頭疼的,對應Java 版本的  POJO 結構和介面存根。

下面是 Dubbo-go 生態習慣寫法中,一個使用 Hessian 序列化,相容 Java 的 Go 客戶端例子。

// UserProvider 客戶端存根類
type UserProvider struct {
  // dubbo標籤,用於適配go側客戶端大寫方法名 -> java側小寫方法名,只有 dubbo 協議客戶端才需要使用
  GetUser  func(ctx context.Context, req int32) (*User, error) `dubbo:"getUser"` 
}

func init(){
  // 註冊客戶端存根類到框架,例項化客戶端介面指標 userProvider
  config.SetConsumerService(userProvider)
}

// 欄位需要與 Java 側對應,首字母大寫
type User struct {
  UserID   string 
  UserFullName string  `hessian:"user_full_name"`
  UserAge  int32 // default convert to "userAge"
  Time time.Time
}

func (u *User) JavaClassName() string {
  return "org.apache.dubbo.User" // 需要與 Java 側 User 類名對應
}

Go 相比於支援方法過載的 Java,對介面的元資料資訊依賴較弱,可以更輕鬆地定位目的方法從而發起呼叫。但本質上,還是需要上面所提到的 “約定好” 的介面資訊,從而保證能正確命中下游方法,以及保證引數解析正確。

在泛化呼叫的情景下,在程式碼上不需要引入 “二方包”, 在增大了自由度的同時,失去了 “二方包” 介面的限制,因此客戶端需要在泛化呼叫傳遞引數時儘可能小心,保證傳遞的引數完全和服務端提供的介面對應,從而正確呼叫。

泛化呼叫包含服務端泛化和客戶端泛化呼叫。如果客戶端泛化是把中間代理當做 consumer 端的反向代理,那麼服務端泛化就是把中間代理當做服務 provider 端的正向代理,把請求轉發到後端真正的服務提供方。服務端泛化,開發者在編寫服務時,不需要宣告具體的引數,框架將請求解析成通用的方法名和引數列表陣列並傳遞至使用者層,開發者編寫的程式碼需要直接操作這些動態的資料,可參考文末的例子。而用的相對較多的是客戶端泛化,即上面聊的,客戶端在程式碼層面並沒有拿到服務端提供的介面依賴,而是通過傳入方法名和引數,由框架生成泛化呼叫請求,從而達到和通過真實介面呼叫一樣的效果。

泛化呼叫請求往往方法名為 $invoke ,包含三個引數,分別是:

  • 真實方法名;
  • 引數名組成的陣列;
  • 引數具體值組成的陣列。

以一個 HSF-go 泛化呼叫請求為例:

// 一個 HSF-go 的客戶端泛化呼叫
genericService.Invoke(context.TODO(), 
                      "getUser", 
                      []string{(&GoUser{}).JavaClassName(), (&GoUser{}).JavaClassName()}, 
                      []interface{}{&GoUser{Name: "laurence"}, &GoUser{Age: 22}}
                     )

框架接收到這三個引數後,會構造出泛化請求,傳送至服務端。

服務端在接收到泛化請求時,會在一層 filter 中過濾出以 $invoke 為方法名的請求,並構造出真實請求結構,向上層傳遞,從而完成呼叫並返回。

以上是 Dubbo 體系泛化呼叫的通用實現,但如果單純站在 Go 語言的角度來設計,並不需要傳遞引數列表型別,服務端可以單純通過方法名定位到方法,再將引數陣列反序列化,獲得真實引數。

2、泛化呼叫與服務運維能力

泛化呼叫的應用場景很廣泛,集團的開發人員接觸最多的泛化呼叫,可能就是 MSE/HSF-ops 平臺提供的服務測試能力。

集團內使用的 MSE 運維平臺是一個強大的、用於 HSF 服務治理的平臺,可以在平臺上配置運維、服務治理能力、進行服務測試,以及商業化版本 MSE 的壓測、流量回放等操作。而其提供的服務測試能力,依賴的就是 HSF 泛化呼叫。當開發人員在平臺上針對一個介面方法發起測試時,會傳入一個 json 引數列表,平臺會將 json 引數列表轉化為 hessian 物件並序列化,構造出上面提到的三引數,並向目的機器發起呼叫,拿到測試返回值。HSF 服務會預設支援泛化呼叫。

除了服務測試,還可以使用泛化呼叫來開發服務閘道器、服務探活、cli 服務測試工具等。

3、泛化呼叫與序列化協議的關係

常見的序列化協議很多,例如 Dubbo/HSF 預設的 hessian2 序列化;還有使用廣泛的 JSON 序列化;以及 gRPC 原生支援的 protobuf(PB) 序列化等等。

提到的這三種典型的序列化方案作用類似,但在實現和開發中略有不同。PB 不可由序列化後的位元組流直接生成記憶體物件,而Hessian和JSON都是可以的。後兩者反序列化的過程不依賴“二方包”,也可以說是存根。一個更好理解的方法是,PB 可以理解為一種類似於對稱加密協議,在客戶端和服務端必須有存根的情況下,才能解析出物件,而 hessian 和 json 不依賴存根,這決定了 pb 的壓縮效果更好。

這也可以解釋為什麼,使用 PB 序列化的 Triple(Dubbo3) 協議並沒有被我們常用的服務運維平臺的測試功能所支援。因為上述泛化呼叫模型只能構造可憑空解析的序列化型別。

如果實在要泛化呼叫 PB 序列化服務,解決方案還是有的,還是用對稱加密舉例,只要我拿到和服務端一致的“金鑰“,我就可以構造出對方可解析的結構,從而發起泛化呼叫。這就是 gRPC 反射服務 的原理,反射服務可以讓客戶端在發起呼叫之前,拿到這份 proto 介面定義檔案,從而獲得對稱加密的“金鑰”,在這份金鑰的基礎上,填寫好引數欄位,就能像正常客戶端一樣發起呼叫了。

HSF-go 在 Dapr 場景的實踐

上面主要聊了 Dubbo 體系的泛化呼叫模型,上面也提到了,泛化呼叫的應用場景非常多,也成為了 Dapr 落地的基礎之一。Dapr 是阿里雲合作的,微軟開源的 CNCF 孵化專案,融合了標準化 API、元件可擴充套件SPI 機制、邊車架構、Serverless 等諸多先進理念,在阿里集團有 FC,跨雲等許多生產落地場景。

1、Dapr Binding 模型

Dapr 標準化 API 理念是非常新穎和實用的,其中 Bindings 構造塊, 是我們服務呼叫解決方案的基礎。

Bindings 最直觀的理解,是介於使用者應用執行時和網路之間的一層流量中介軟體。

上圖可以解釋基於 Binding 的整條呼叫鏈路,由使用者應用執行時呼叫 Dapr 標準化介面從而發起呼叫。由 Dapr 執行時將流量交給可擴充套件的 Binding 構造塊,Dapr 可以這種統一化介面和可擴充套件能力,很方便地支援多種協議的切換,按需啟用。如圖中伸展出來的 HSF、Dubbo 支援。

被啟用的例如 HSF-go 構造塊將接管這一請求,將來自應用的標準化的請求頭和請求體解析出來,生成 HSF 協議請求,Dapr 邊車一般不會擁有下游服務二方包,因此這一請求一定是泛化呼叫請求。

當然,在請求發出之前,早已完成了服務發現過程,這是使用者以及應用執行時無感的,由 Dapr 來接管和封裝。上面提到的泛化請求在完成服務發現之後,即可被髮送至目的機器 ip,被下游的 Inbound Binding 的 HSF-go 實現所接收和處理,這個下游的元件對應上面提到的“服務端泛化呼叫”,他接受任何 HSF 請求。下游將 HSF 協議解析出來,引數從泛化呼叫的三個引數標準化為正常請求引數後,通過 Dapr 提供的 Callback 機制傳遞至應用執行時。

在這一過程中,泛化呼叫扮演了極其重要的角色,在客戶端負責出流量的 HSF 協議泛化呼叫發起,在服務端負責入流量的泛化呼叫解析和傳遞。

我認為,Dapr 繫結的網路協議模型,是 RPC 協議進一步抽象的體現。將所有的 RPC 協議抽象為 metadata(元資料)和 body 兩部分,使用者應用/SDK 側只需要關心這兩部分的內容。一旦將這個抽象的請求結構交給 Dapr,具體協議的生成,就由具體啟用的構造塊來做了,這是我認為 Dapr 提供的一種很精巧的服務呼叫抽象設計。

2、序列化陣列透傳的設計

上面提到的入流量與出流量元件都是泛化呼叫的實現,但如果細究,並不是第一節我們提到的傳統泛化呼叫。

傳統泛化呼叫的入參是結構,呼叫過程涉及到序列化過程。在 Dapr 這種邊車場景下,一次完整的 RPC 呼叫將會引入至少六次序列化/反序列化過程,這成本是巨大的。

因此在設計中,並沒有使用標準泛化呼叫過程,而是將序列化過程省略掉了,只保留了應用側的一次序列化,Dapr 邊車針對引數部分只進行透傳處理。這樣來,大大減少了無謂的消耗。

這樣一來,在客戶端 Outbound 的實現,就成了針對如下泛化呼叫介面的使用:

//  args 引數為序列化後的byte陣列
ProxyInvokeWithBytes(ctx context.Context, methodName string, argsTypes []string, args [][]byte) ([]byte, error)


在服務端Inbound 的實現,也成了針對byte陣列型別引數的泛化呼叫


// inbound 入參
type RawDataServiceRequest struct {
  RequestContext *core.RequestContext
  Method         string
  ArgsTypes      []string
  Args           [][]byte // args 引數為序列化後的byte陣列
  Attachment     map[string]interface{}
  RequestProps   []byte
}

相當於在泛化呼叫的基礎上,刪除了序列化操作,將請求引數透傳。

HSF-go 服務代理的設計

釘釘團隊擁有很多 Go 語言落地場景,在 Dubbo-go 生態專案的發展過程中提供了諸多幫助與實踐。

在跨叢集通訊解決方案中,代理閘道器是必不可少的,大多數閘道器需要運維人員手動進行流量配置。部分閘道器對網路協議存在要求,例如 envoy,因此中介軟體團隊推出基於 Http2 的 Dubbo3(Triple) 協議的原因之一,就是為了適配閘道器。

在跨叢集 RPC 場景下,理想情況是在閘道器層不需要進行協議轉換,並且不需要進行序列化/反序列化過程,並且將服務治理能力融合在閘道器內部,從而減少資源消耗和運維成本。

這也提出了一種訴求,在集團內跨雲場景下,我們需要建立一個支援原生 HSF 協議的代理閘道器,從而允許叢集外部的客戶端在無感的情況下,將請求切流量至叢集內部,由閘道器接受來自外界的 HSF 請求,並動態進行服務發現流程,將請求流量轉發至叢集內對應服務提供者。可以想到,泛化呼叫在這個過程中將扮演重要角色。

我們沿著之前 Dapr 的思路,如上圖所示,將視角從整個呼叫鏈路轉移到單個例項上,可以看到一個例項可以接受泛化請求,並也可以發起泛化請求,在泛化過程中不涉及序列化過程。這個我們所關注的例項,就是一個閘道器的抽象表現。

擁有了這樣的閘道器,我們可以實現客戶端無感的跨叢集呼叫。在必要的情況下,可以在客戶端所在環境進行代理註冊。

這樣的閘道器是單向的,可以處理從外部進入內部的流量,如果希望雙向打通,跨叢集的統一化註冊中心將是必要的。在這種情況下,閘道器需要根據流量查詢多個註冊中心的資訊,從而保證鏈路正確。

總結

HSF 是阿里集團 RPC/服務治理 領域的標杆,Go 語言又因為其高併發,雲原生的特性,擁有廣闊的發展前景和實踐場景,服務代理模型只是一種落地場景,除此之外,還有更多的應用場景值得我們在研發的過程中去探索和總結。

Dubbo/HSF 生態、Dubbo-go 技術體系將攜手使用者一同打磨與實踐。

原文連結

本文為阿里雲原創內容,未經允許不得轉載。