目錄
- 前言
- 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
服務啟動後會列印如下日志,說明服務啟動成功
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL0IWZ0QjM1EWM0IjYiRmNiRzNmRDOjZjYyEmN3QTMhR2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
測試
發起一個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"}
并且在代理服務上産生通路日志:
到這裡一個簡單的示範就成功了
結構和原理
主要是根據grpc的反射的描述,生成http路由,并動态完成json和proto的映射。
更進一步的原理和結構,未完待續~
尾語
目前版本還隻是一個比較初級的版本,功能還很初級。
還有很多功能需要完善,架構也可能會有大的變動,所有上一節并沒有較長的描述。
作者預計但不承諾會繼續完成下面的内容。
- restful支援:這個功能是P0級,在我go版本的grpc動态代理服務中經常被用到,在可預計的規劃裡一定會實作。
- 事件系統:該功能是為了友善二次開發,很有必要。也有可能用中間件模式,類似traefik
- 負載均衡/sidecar:負載均衡是為了用在服務網關上,sidecar是用在pod裡,二者會選一個實作,我傾向于前者,和我之前寫的rust-ingress關聯上。這裡自薦一波rust-ingress項目:https://gitee.com/yutiandou/rust-ingress
- 性能優化,這個會一直持續做下去,歡迎有性能極緻追求的小夥伴能夠共同前進
- 實時反射:目前是通過配置檔案,在啟動的時候加載服務源。好的(懶人)方案是proto檔案變化後能夠實時監控到,下一步會完成這個功能。
歡迎有興趣的小夥伴提出建議,并熱烈歡迎大家參與進來。