天天看點

基于rust的,gRPC動态代理,無需proto檔案自動http轉gRPC前言grpc實踐結構和原理尾語

目錄

  • 前言
  • grpc
  • 實踐
    • github位址
    • 第一步
    • 第二步
    • 第三步
    • 第四步
    • 測試
  • 結構和原理
  • 尾語

前言

 溝通服務間接口内容(尤其是前後端接口),是非常讓人頭疼的事。極其容易扯皮。接口文檔寫起來也很痛苦,每個字段的改動都需要及時更新,否則就會出問題。服務端通信如果用rpc通信的話,一般會有proto或者thrift檔案。這個檔案很長時間裡被我們當成接口文檔用,用着用着發現,真tm好用。既減少了扯皮,還不用寫接口文檔。那可不可以用grpc和前端通信那,一開始我們的做法是用grpc-gateway。把grpc的接口映射成http接口。但這種方式需要編譯gateway的pb檔案,對服務也是有侵入的。後來随着我在公司的時間越來越長,接手的服務越來越多(經常需要發版的項目就有十幾個),這種方式維護起來十分糟心,後一直想尋求一種一勞永逸的解決方法?

 本人之前很長一段時間從事saas,paas的開發。對于一些服務而言,既要提供grpc通路的能力,也要對外提供http通路的能力(做saas就是這麼卑微)。并且這種需求通常不是一開始就提出來的,而是對一個已經穩定運作的龐大的服務做改造。而這會導緻爬屎山,鑒權不一緻等一系列問題。那有沒有一種無侵入的協定轉換能力?

  grpc是基于http2協定,而http2是長連接配接。這對k8s部署的服務非常不友好。在這我猜肯定有很多小夥伴說可以用linked,istio等基于Service Mesh的解決方案。一是這些技術是近兩年才穩定下來的,以前問題很多,根本不敢用,當然現在istio已經流行起來了,可以很完美的做到grpc的負載均衡和很優秀的流量管理。但依然存在不滿足實際需求的情況,比如對grpc流量做精細過濾,細到每個請求的精準控制。這種二次開發的需求是很難在istio上完成。尤其是對一些小公司而言。

基于很多原因的考慮,最終誕生了搞一個grpc動态代理的想法,并初步實作。

grpc

  在雲原生,容器化,微服務的大背景下。rpc也徹底奠定了服務間通信協定的霸主地位。衆多rpc架構中grpc和thrift是最流行最受歡迎的rpc架構。在實際開發中,我兩個架構都有深入的使用過。相較而言,我更喜歡grpc的風格。背靠google大樹(已經是CNCF孵化項目),多語言都支援,基于protobuf極緻編碼和急速傳輸,等等優點就不一一詳述。有興趣的可以看grpc官網,上面吹的比我吹的好。

實踐

github位址

https://github.com/woshihaoren4/grpc-proxy

第一步

先将代碼clone下來到本地,我這裡用的mac系統

因為編譯需要cargo環境,需要先安裝rust,參考教程:https://www.rust-lang.org/zh-CN/ 上面有很詳細的中文教程。

我安裝的是:

rustc 1.66.0 (69f9c33d7 2022-12-12)

版本

第二步

先進入項目根目錄,運作起測試例子

./example/helloworld server

沒有權限的話,需要先權重限,然後再運作
chmod +x ./example/helloworld
           

這個例子使用golang編寫的簡單的grpc服務,實作上沒有啥特殊的部分,值得注意的是需要給grpc服務加上反射

//grpc的HelloWorld方法實作,就是在字元串上加一個 world
func (s *Service) HelloWorld(ctx context.Context, req *proto.HelloWorldRequest) (*proto.HelloWorldResponse, error) {
        return &proto.HelloWorldResponse{Response: req.Request + " world"}, nil
}
//這裡相當于main函數
func server(ctx *wdevent.Context) error {
        ls, _ := net.Listen("tcp", ":8888")
        gs := grpc.NewServer()
        proto.RegisterHelloWorldServiceServer(gs, new(Service))
        reflection.Register(gs)
        logrus.Infoln("grpc server start workd ....")
        gs.Serve(ls)
        return nil
}
           

再看一下pb檔案,需要注意的是在option裡指明 需要映射的http的路徑和方法

syntax = "proto3";

package proto;
option go_package = "./proto";
import "google/api/annotations.proto";

// HelloWorld Service
service HelloWorldService {
  rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse){
    option (google.api.http) = {
      post: "/api/v2/hello"
      body: "*"
    };
  };
}
message HelloWorldRequest {
  string request = 1;
}
message HelloWorldResponse {
  string response = 1;
}
           

第三步

修改配置檔案如下,路徑:./src/config/config.toml。目前項目中的配置檔案已經寫好了這些内容,不需要再配置什麼了。當然不放心也可以檢視一下。

[[proxy_sink]]
name = "hello"
addr = "127.0.0.1:8888"
           
  • 需要在addr中指明上面服務的位址和端口。

第四步

另起終端,編譯并運作項目

cargo run -- run
           

國内沒有科學上網的話會有些慢,主要是下載下傳包比較慢,可以用清華源或者其他的,教程參考:https://www.w3cschool.cn/cargo_guide/cargo_guide-uxdg3l62.html

服務啟動後會列印如下日志,說明服務啟動成功

基于rust的,gRPC動态代理,無需proto檔案自動http轉gRPC前言grpc實踐結構和原理尾語

測試

發起一個http請求

curl --location --request POST 'http://127.0.0.1:6789/api/v2/hello' \
--header 'Content-Type: application/json' \
--data-raw '{
    "request": "hello"
}'
           

可以看到傳回内容:

{"response": "hello world"}

并且在代理服務上産生通路日志:

基于rust的,gRPC動态代理,無需proto檔案自動http轉gRPC前言grpc實踐結構和原理尾語

到這裡一個簡單的示範就成功了

結構和原理

主要是根據grpc的反射的描述,生成http路由,并動态完成json和proto的映射。

更進一步的原理和結構,未完待續~

尾語

目前版本還隻是一個比較初級的版本,功能還很初級。

還有很多功能需要完善,架構也可能會有大的變動,所有上一節并沒有較長的描述。

作者預計但不承諾會繼續完成下面的内容。

  • restful支援:這個功能是P0級,在我go版本的grpc動态代理服務中經常被用到,在可預計的規劃裡一定會實作。
  • 事件系統:該功能是為了友善二次開發,很有必要。也有可能用中間件模式,類似traefik
  • 負載均衡/sidecar:負載均衡是為了用在服務網關上,sidecar是用在pod裡,二者會選一個實作,我傾向于前者,和我之前寫的rust-ingress關聯上。這裡自薦一波rust-ingress項目:https://gitee.com/yutiandou/rust-ingress
  • 性能優化,這個會一直持續做下去,歡迎有性能極緻追求的小夥伴能夠共同前進
  • 實時反射:目前是通過配置檔案,在啟動的時候加載服務源。好的(懶人)方案是proto檔案變化後能夠實時監控到,下一步會完成這個功能。

歡迎有興趣的小夥伴提出建議,并熱烈歡迎大家參與進來。

繼續閱讀