資安

Dubbo 跨語言調用神獸:dubbo-go-pixiu

Pixiu 是什麼

在回答 Pixiu 是什麼之前,我們簡單解釋一下 Dubbo 是什麼。Dubbo 是一個開源的高性能 RPC 框架,有著豐富的服務治理能力以及優秀的擴展能力。Dubbo 更擴展出 Dubbo-go​,為用戶提供了 Golang 的 Dubbo 解決方案,打通了兩種語言之間的隔閡,使 Dubbo 更加貼近雲原生。

皮球.png
Dubbo-go 作為 Golang 服務,實現與 Dubbo 服務之間的相互調用。然而,在日常使用場景中,用戶往往有把 Dubbo 服務以 RESTful 風格向外暴露的需求同時也要兼顧內部 Dubbo 調用。為了解決這種場景,作為 Dubbo API 網關的 Pixiu​ (中文: 貔貅, 曾用名 dubbo-go-proxy) 便應運而生。之所以採用 Pixiu 這個名稱,是因為 Java 同類產品 Zuul 的意象是一個西方怪獸,Pixiu 作為一個國產產品,就用了我們中國的一個類似的神獸貔貅作為項目名稱。也同時表達了 Dubbo 社區希望擴展出一整套雲原生生態鏈的決心。

目前 Dubbo 多語言生態,發展最好的自然是 Java,其次是 Golang,其他語言都差強人意。dubbo-go-pixiu 項目是一個基於 dubbo-go 發展起來的項目,目前接口協議層支持的是七層的 HTTP 請求調用,計劃在未來的 0.5 版本中支持 gRPC 請求調用,其另外一個使命是作為一種新的 dubbo 多語言解決方案。

為什麼使用 Pixiu

Pixiu 是基於 Dubbogo 的雲原生、高性能、可擴展的微服務 API 網關。作為一款網關產品,Pixiu 幫助用戶輕鬆創建、發佈、維護、監控和保護任意規模的 API ,接受和處理成千上萬個併發 API 調用,包括流量管理、 CORS 支持、授權和訪問控制、限制、監控,以及 API 版本管理。除此以外,作為 Dubbo 的衍生產品,Pixiu可以幫助 Dubbo 用戶進行協議轉換,實現跨系統、跨協議的服務能力互通。

Pixiu 的整體設計遵守以下原則:

  1. High performance: 高吞吐量以及毫秒級的延時。
  2. 可擴展: 通過 go-plugin,用戶可以根據自己的需求延展 Pixiu 的功能。
  3. 簡單可用: 用戶通過少量配置,即可上線。

Pixiu 的特性及核心功能

  • 為 RESTful API 和 Dubbo API 提供支持

非 RESTful 風格的 API 和 Dubbo 協議的服務往往需要修改才可以以 RESTful API 風格對外開放。Pixiu 提供協議轉換功能,通過 Pixiu,開發者可以將自己的 HTTP API 或 Dubbo API 通過配置,以 RESTful API 風格對外開放。v0.2.1 版本已支持基於泛化調用的 HTTP 至 Dubbo 的協議轉換以及 HTTP 協議的轉發。在後續的版本,社區將會增加對 gRPC 和 http2 協議的支持。

  • 面向用戶的配置方式

一般的網關的配置往往繁瑣且複雜。Pixiu,目標作為一款易用的網關產品,在設計上擁有三層配置層級,Gateway 層全局配置, API resource 層配置以及 HTTP verbs 方法層配置。通過三個不同層級的配置,既可以實現深度的定製,亦支持統一的默認配置;同時,支持本地的配置文件,亦可使用統一配置服務器。另外,還提供控制檯模塊,通過控制檯模塊,支持配置的熱更新。Pixiu 配套配套的控制檯界面也在同步開發中。

  • 通用功能的集成

重試、熔斷、流量控制、訪問控制等通用功能不再需要在每個後端服務上重複實現。使用 Pixiu,通過配置 filter ,開發者可以進行全局的控制,亦可以根據 API 配置各自的規則。因此開發者可以專注於業務邏輯和服務,而不是將時間用在維護基礎設施上。

  • 可擴展

不同的使用場景有著各自獨特的需求。為滿足不同用戶的定製化需求,Pixiu 使用了插件模式。開發者可以通過編寫 go plugin,將自身特有的業務邏輯以 filter 形式內嵌至 Pixiu 網關中,實現諸如企業登錄鑑權等功能。
截屏2021-07-15 下午6.23.32.png

圖 1: Pixiu 核心功能列表

Pixiu 的架構設計

**image.png
圖 2: Pixiu 架構

貔貅: 即 dubbo-go-pixiu,由四個主要模塊:Listener、Router、Filters 和 Clients 組成;
Dubbo Cluster: Dubbo 服務所在集群,包含一個或多個 Dubbo Services;
Other Cluster: Dubbo 以外的服務所在集群,現支持 HTTP 服務,未來將拓展支持 gRPC 等其他服務;
Registry Center: 註冊中心,維護每個業務服務的調用地址信息;
Metadata Center: 元數據中心,維護每個業務服務的配置信息以及存儲 Pixiu 本身的配置信息。

作為 Dubbo 所衍生的 API 網關,Pixiu 使用 Golang 搭建,主要因為: 1. Golang 的 G-M-P,net poller 等特性使 Golang 非常適合構建IO密集型應用;2. 使用 Golang 可以直接引入 Dubbo-go 中的一些組建,簡化開發。

整個Pixiu大致可以拆分為四個主要模塊:Listener、Router、Filters 和 Client

1、Listener

在 Pixiu 中,Listener 代表外部可以訪問Pixiu的方式。通過配置指定協議類型,地址,端口等屬性,暴露 Gateway。現階段暫支持 HTTP 協議,未來將會加入 gRPC。

listeners: 
  - name: "net/http" 
    address: 
      socket_address: 
        protocol_type: "HTTP" 
        address: "0.0.0.0" 
        port: 8888 
    config: 
      idle_timeout: 5s 
      read_timeout: 5s 
      write_timeout: 5s

2、Router

Router 是 Pixiu 的路由組件。根據配置文件,Pixiu 將對外暴露的 URLs 以樹的形勢存儲於內存中,當請求到了 router 組件時,即會根據 URL 及 HTTP 方法查找到對應的後端服務及其 API 配置,並將信息封裝於請求中,為後續 filter,及 client 的調用提供足夠的內容。

現階段,Router 提供以下功能:

  • 支持請求一對一轉發路由配置或 wildcard 路由配置。
  • 支持 HTTP 請求的轉發到後端 HTTP 服務。
  • 支持 HTTP 請求轉化為 dubbo 泛化調用請求。

3、Filters

Filter 是 Pixiu 實現額外功能及其擴展性的主要組件。其實現類似於 Dubbo-go 中的 filter,根據配置中 filter 的指定,生成調用鏈,從而在調用後端服務前,將各 filter 中的邏輯運行一遍,實現節流,日誌等功能。

用戶如果需要客製化的 filter,可通過編寫 go-plugin 實現。在配置中,可通過類似如下配置,加載 .so文件,並在 API config 中指定使用的 plugin group,plugin name 實現。


pluginFilePath: "" 
pluginsGroup: 
  - groupName: "group1" 
    plugins: 
      - name: "rate limit" 
        version: "0.0.1" 
        priority: 1000 
        externalLookupName: "ExternalPluginRateLimit" 
      - name: "access" 
        version: "0.0.1" 
        priority: 1000 
        externalLookupName: "ExternalPluginAccess" 
  - groupName: "group2" 
    plugins: 
      - name: "blacklist" 
        version: "0.0.1" 
        priority: 1000 
        externalLookupName: "ExternalPluginBlackList"

4、Client

Client 負責調用具體服務。現階段,Pixiu 支持 HTTP 與 Dubbo 的後端服務。社區將逐漸增加 gRPC 等其他 Client 以滿足不同的協議。

HTTP client 的實現相對簡單,根據 Router 中獲取的後端服務信息,通過 Golang 官方包 net/http 生成請求並調用。

Dubbo client 的實現對比 HTTP client 會稍微複雜,其基礎為 Dubbo 服務的泛化調用。泛化調用技術是 Dubbo 提供的一個很基礎的功能只需要知道調用的方法名、參數類型和返回值類型,即可發起服務調用。客戶端對服務端的泛化調用既可以通過註冊中心發現服務,也可以直連服務端,實現對服務的動態調用。

如下面代碼所示,Pixiu 通過動態配置 referenceConfig,然後通過 GetRPCService 生成 Dubbo 的 Generic Client(泛化調用客戶端)進行下一步的調用。

 referenceConfig := dg.NewReferenceConfig(irequest.Interface, context.TODO())
  referenceConfig.InterfaceName = irequest.Interface
  referenceConfig.Cluster = constant.DEFAULT_CLUSTER
  var registers []string
  for k := range dgCfg.Registries {
    registers = append(registers, k)
  }
  referenceConfig.Registry = strings.Join(registers, ",")

  if len(irequest.DubboBackendConfig.Protocol) == 0 {
    referenceConfig.Protocol = dubbo.DUBBO
  } else {
    referenceConfig.Protocol = irequest.DubboBackendConfig.Protocol
  }

  referenceConfig.Version = irequest.DubboBackendConfig.Version
  referenceConfig.Group = irequest.Group
  referenceConfig.Generic = true
  if len(irequest.DubboBackendConfig.Retries) == 0 {
    referenceConfig.Retries = "3"
  } else {
    referenceConfig.Retries = irequest.DubboBackendConfig.Retries
  }
  dc.lock.Lock()
  defer dc.lock.Unlock()
  referenceConfig.GenericLoad(key)
  clientService := referenceConfig.GetRPCService().(*dg.GenericService)

實際上,在泛化調用的客戶端中,實際執行泛化調用的關鍵步驟是 Dubbo-go 中的 generic_filter (如下代碼片段)。在調用 generic_filter 的 Invoke 時,約定 invocation 參數列表第一個為方法名,第二個為參數類型列表,第三個為參數值列表。generic_filter 將用戶請求的參數值列表轉化為統一格式的 map(代碼中的 struct2MapAll ),將類( golang 中為 struct )的正反序列化操作變成 map 的正反序列化操作。這使得無需 POJO 描述通過硬編碼注入 hessain 庫,從而完成 Dubbo 服務的泛化調用。

func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
  if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
    oldArguments := invocation.Arguments()
    if oldParams, ok := oldArguments[2].([]interface{}); ok {
      newParams := make([]hessian.Object, 0, len(oldParams))
      for i := range oldParams {
        newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i])))
      }
      newArguments := []interface{}{
        oldArguments[0],
        oldArguments[1],
        newParams,
      }
      newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
      newInvocation.SetReply(invocation.Reply())
      return invoker.Invoke(ctx, newInvocation)
    }
  }
  return invoker.Invoke(ctx, invocation)
}

總結

通過上面的四個模塊以及註冊中心的簡單介紹不難發現,當請求通過 listener 被 Pixiu 接收後,請求被傳入 router 中。router 根據接口的配置,從原請求中找到目標後端服務連同相關API配置下發到 filter 組件。filter 組件根據原請求、 API 配置等信息順序執行,最終請求到達 client, 通過 client 調用後端服務。

Pixiu的未來

1.png
圖 3: Pixiu 迭代里程碑

Pixiu 作為網關產品外,其衍生項目也會在我們的未來計劃中,主要目的是提供更好的可用性。例如,由於 Golang 語言缺乏原生的註解, 因此 Dubbo-go 需要通過配置文件方式生成服務的元數據寫入註冊中心。開課啦教育公司相關同學寫了一個掃描代碼的工具 _https://github.com/jack15083/dubbo-go-proxy-tool_,在每個 RPC 服務方法前加上對應的註釋,從而在服務啟動前通過掃描註釋生成元數據。Pixiu 也計劃在未來的版本上通過提供 package,允許服務通過註釋藉助 _https://github.com/MarcGrol/golangAnnotations _生成 API 配置並註冊到 Pixiu 上。

Pixiu 目前的定位是一個七層協議網關,其最初版本是被定義成一個 Dubbo 的服務網關。作為雲時代的產品,Pixiu 的發展方向必然是面向雲原生的。現在的版本為0.2.1, 已經實現基本的 Dubbo/Http 服務代理和部分的網關通用功能。目前正在開發中的 0.4 及其後續版本支持 gRPC 和 Spring Cloud 服務調用, 後續還將提供 MQ 服務支持。另外,社區將繼續優化配置方式,降低用戶的使用難度,繼續優化官方的 filter,使 Pixiu 可以在官方層面實現更多的網關通用功能。

image.png

在未來的一年內,社區計劃支持 xDS API,將 Pixiu 演化為 Dubbo mesh 的 sidecar。其最終目的就是:在現有的 dubbo mesh 形態中演化出 Proxy Service Mesh 形態。基於這個形態,Js、Python、PHP、Ruby 和 Perl 等腳本語言程序除了收穫 dubbo mesh 原有的技術紅利之外,大概率還能收穫性能上的提升。

Pixiu 在 Dubbo Mesh 中的終極目的是:把東西向和南北向數據面流量逐步統一 Pixiu 中的同時,讓它逐步具備 Application Runtime 的能力,作為 Dubbo 多語言生態的關鍵解決方案。

dubbogo 社區【釘釘群號23331795】與 dubbogo 同在。

馮振宇,Apache Dubbo Committer,目前負責管理香港一家消費品公司的IT部門整個團隊。2020 年夏天 偶然看到了介紹 dubbogo 的文章後加入了 dubbogo 社區,目前在主導 Pixiu 0.4.0 版本的開發。
_

點擊https://developer.aliyun.com/community/cloudnative,瞭解更多雲原生內容!

Leave a Reply

Your email address will not be published. Required fields are marked *