泛化呼叫
泛化呼叫是 Dubbo-Go 的一種特殊呼叫方式,允許中間節點在沒有介面信息的情況下傳遞呼叫信息,常用於測試和閘道器場景。泛化呼叫支持 Dubbo 和 Triple 協定,但目前序列化方案僅支持 Hessian。
背景
為了便於理解,本文檔以閘道器使用場景為例來介紹泛化呼叫。我們先來看看普通的呼叫(非泛化呼叫)。下圖包含消費者和提供者兩個關鍵角色(endpoint 在下文中用於表示消費者或提供者),並且每個角色都有一個 org.apache.dubbo.sample.User 介面的定義。假設在呼叫行為中需要使用 org.apache.dubbo.sample.User 介面。
RPC 需要透過網路媒體傳輸,因此數據不能以 go struct 形式傳輸,而必須以二進制形式傳輸。這就要求消費者端在傳輸前將實現 org.apache.dubbo.sample.User 介面的結構序列化為二進制格式。同樣,對於提供者端,需要將二進制數據反序列化為結構信息。簡而言之,普通呼叫要求介面信息在每個端點必須具有相同的定義,以確保數據序列化和反序列化的結果與預期一致。
在閘道器場景下,閘道器不可能儲存所有介面定義。例如,一個閘道器需要轉發 100 個服務呼叫,每個服務所需的介面數量為 10 個。常見的呼叫需要所有 1000 (100 * 10) 個介面定義都預先儲存在閘道器中,這顯然難以實現。那麼,有沒有辦法在不預先儲存介面定義的情況下正確轉發呼叫呢?答案是肯定的,這就是泛化呼叫的用途。
原理
泛化呼叫的本質是將複雜結構轉換為通用結構。這裡所說的通用結構指的是 Map、字串等,閘道器可以順利地解析和傳輸這些通用結構。
目前,Dubbo-go v3 只支援 Map 泛化(預設)。讓我們以 User 介面為例,其定義如下。
// definition
type User struct {
ID string
name string
Age int32
}
func (u *User) JavaClassName() string {
return "org.apache.dubbo.sample.User"
}
假設呼叫一個服務需要一個 user 作為輸入參數,其定義如下。
// an instance of the User
user := &User{
ID: "1",
Name: "Zhangsan",
Age: 20,
}
那麼,使用 Map 泛化時,user 將會被自動轉換為 Map 格式,如下所示。
usermap := map[interface{}]interface{} {
"iD": "1",
"name": "zhangsan",
"age": 20,
"class": "org.apache.dubbo.sample.User",
}
需要注意的是
- Map 泛化會自動將首字母小寫,即 ID 會被轉換為 iD。如果需要與 Dubbo-Java 對齊,請考慮將 ID 改為 Id;
- Map 中會自動插入一個類別欄位,用於標識原始的介面類別。
使用
泛化呼叫對服務提供方是透明的,也就是說,服務提供方不需要任何顯式設定即可正確處理泛化請求。
基於 Dubbo URL 的泛化呼叫
基於 Filter 泛化的呼叫對消費者是透明的,典型的應用場景是閘道器。這種方式需要 Dubbo URL 包含泛化呼叫標識,如下所示。
dubbo://127.0.0.1:20000/org.apache.dubbo.sample.UserProvider?generic=true&...
這個 Dubbo URL 表達的含義是
- RPC 協定為 dubbo;
- 呼叫 127.0.0.1:20000 上的 org.apache.dubbo.sample.UserProvider 介面;
- 使用泛化呼叫 (generic=true)。
Consumer 端的 Filter 會根據 Dubbo URL 攜帶的設定自動將普通呼叫轉換為泛化呼叫,但需要注意的是,這種方式下,響應結果是以泛化格式返回的,不會自動轉換成對應的物件。例如,在 map 泛化模式下,如果需要返回 User 類別,那麼消費者將會得到一個對應 User 類別的 map。
手動泛化呼叫
手動泛化呼叫發起的請求不經過 filter,因此需要消費者端顯式發起泛化呼叫。典型的應用場景是測試。在 dubbo-go-samples 中,為了測試方便,使用了手動呼叫。
泛化呼叫不需要建立設定檔 (dubbogo.yaml),但需要在程式碼中手動設定註冊中心、Reference 等資訊。初始化方法封裝成 newRefConf 方法,如下所示。
func newRefConf(appName, iface, protocol string) config.ReferenceConfig {
registryConfig := &config.RegistryConfig{
Protocol: "zookeeper",
Address: "127.0.0.1:2181",
}
refConf := config.ReferenceConfig{
InterfaceName: iface,
Cluster: "failover",
Registry: []string{"zk"},
Protocol: protocol,
Generic: "true",
}
rootConfig := config.NewRootConfig(config.WithRootRegistryConfig("zk", registryConfig))
_ = rootConfig.Init()
_ = refConf.Init(rootConfig)
refConf. GenericLoad(appName)
return refConf
}
newRefConf 方法接收三個參數,分別是
- appName:應用程式名稱;
- iface:服務介面名稱;
- protocol:RPC 協定,目前只支援 dubbo 和 tri(Triple 協定)。
在上述方法中,為了保持函式簡潔,註冊中心設定為固定值,即使用 127.0.0.1:2181 的 ZooKeeper 作為註冊中心,在實際應用中可以根據實際情況自由定制。
我們可以輕鬆地獲得一個 ReferenceConfig 實例,暫命名為 refConf。
refConf := newRefConf("example.dubbo.io", "org.apache.dubbo.sample.UserProvider", "tri")
然後我們就可以對 org.apache.dubbo.sample.UserProvider 服務的 GetUser 方法發起泛化呼叫。
resp, err := refConf.
GetRPCService().(*generic.GenericService).
Invoke(
context. TODO(),
"GetUser",
[]string{"java. lang. String"},
[]hessian. Object{"A003"},
)
GenericService 的 Invoke 方法接收四個參數,分別是
- context;
- 方法名稱:在此範例中,指的是呼叫 GetUser 方法;
- 參數類型:GetUser 方法接受一個字串類型的參數。如果目標方法接受多個參數,可以寫成
[]string{"type1", "type2", ...}
,如果當前方法沒有參數,則需要填入一個空陣列[]string{}
; - 實際參數:寫法與參數類型相同。如果是無參數函數,仍然應填入一個空陣列
[]hessian.Object{}
。
注意:在目前的版本中,呼叫無參數方法時會發生程式崩潰問題。
相關閱讀:[Dubbo-go 服務代理模型]