我所在公司的項目是采用基於Restful的微服務架構,隨着微服務之間的溝通越來越頻繁,就希望可以做成用rpc來做內部的通訊,對外依然用Restful。於是就想到了google的grpc。
使用grpc的優點很多,二進制的數據可以加快傳輸速度,基於http2的多路復用可以減少服務之間的連接次數,和函數一樣的調用方式也有效的提升了開發效率。
不過使用grpc也會面臨一個問題,我們的微服務對外一定是要提供Restful接口的,如果內部調用使用grpc,在某些情況下要同時提供一個功能的兩套API接口,這樣就不僅降低了開發效率,也增加了調試的復雜度。於是就想着有沒有一個轉換機制,讓Restful和gprc可以相互轉化。
在網上看到一個解決方案,https://github.com/grpc-ecosystem/grpc-gateway,簡單的說就是有一個網關服務器負責轉化和代理轉發。
如下圖:
安裝
首先要安裝ProtocolBuffers 3.0及以上版本。
mkdir tmp
cd tmp
git clone https://github.com/google/protobuf
cd protobuf
./autogen.sh
./configure
make
make check
sudo make install
然后使用go get獲取grpc-gateway。
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go
這里最好把編譯生成的二進制文件的目錄放在$PATH
中,可以把$GOPATH/bin
放入$PATH
中。
示例
本示例是基於我的上一篇博客《google的grpc在glang中的使用》中的示例,如果有必要請先了解上一篇博客。
示例代碼獲取地址:https://github.com/andyidea/go-example。
代碼文件結構如下
└── src
└── grpc-helloworld-gateway
├── gateway
│ └── main.go
├── greeter_server
│ └── main.go
└── helloworld
├── helloworld.pb.go
├── helloworld.pb.gw.go
└── helloworld.proto
我們還是先看一下協議文件。helloworld.proto有一些變動,引入了google官方的api相關的擴展,為grpc的http轉換提供了支持。
具體改動如下:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
import "google/api/annotations.proto";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
和之前的proto文件比較,新的文件增了
import "google/api/annotations.proto";
和
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
這里增加了對http的擴展配置。
然后編譯proto文件,生成對應的go文件
cd src/grpc-helloworld-gateway
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:. \
helloworld/helloworld.proto
這里生成了helloworld/helloworld.pb.go文件。
helloworld.pb.go是server服務需要的,下一步我們需要使用protoc生成gateway需要的go文件。
cd src/grpc-helloworld-gateway
protoc -I/usr/local/include -I. \
-I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
helloworld/helloworld.proto
這里生成了helloworld/helloworld.pb.gw.go文件。這個文件就是gateway用來的協議文件,用來做grpc和http的協議轉換。
協議文件處理完畢,就需要寫gateway代碼了。
gateway代碼如下:
package main
import (
"flag"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
gw "grpc-helloworld-gateway/helloworld"
)
var (
echoEndpoint = flag.String("echo_endpoint", "localhost:50051", "endpoint of YourService")
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
首先echoEndpoint存儲了需要連接的server信息,然后將這些信息和新建的server用gw.go中的RegisterGreeterHandlerFromEndpoint進行一個注冊和綁定,這時低層就會連接echoEndpoint提供的遠程server地址,這樣gateway就作為客戶端和遠程server建立了連接,之后用http啟動新建的server,gateway就作為服務器端對外提供http的服務了。
代碼到此就完成了,我們測試一下。
先啟動greeter_server服務,再啟動gateway,這時gatway連接上greeter_server后,對外建立http的監聽。
然后我們用curl發送http請求
curl -X POST -k http://localhost:8080/v1/example/echo -d '{"name": " world"}'
{"message":"Hello world"}
流程如下:curl用post向gateway發送請求,gateway作為proxy將請求轉化一下通過grpc轉發給greeter_server,greeter_server通過grpc返回結果,gateway收到結果后,轉化成json返回給前端。
這樣,就通過grpc-gateway完成了從http json到內部grpc的轉化過程。