上一篇无脑吹了一波 Protobuf,这篇就是实践了,我们项目里基本上都是用 Grpc + Grpc Gateway,即作为内部 API 的通讯,也对外提供 Restful Api。
GRPC
GRPC概述
Google对Grpc的定义:
GRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
在 GRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,GRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 GRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
GRPC特性
- 强大的IDL,使用Protocol Buffers作为数据交换的格式,支持v2、v3(推荐v3)
- 跨语言、跨平台,也就是Grpc支持多种平台和语言
- 支持HTTP2,双向传输、多路复用、认证等
Grpc-Gateway
Grpc-Gateway概述
Grpc-Gateway 是 ProtoC 的插件。ProtoC 是 Protobuf 底层解析器,它读取 GRPC 服务定义,并生成一个反向代理服务器,将一个 REST 的 JSON API 转换成 GRPC。这个服务器是根据你的 GRPC 定义中的自定义选项生成的。Grpc-Gateway 结构图如下:
安装
protoc安装
在这里我以 Windows 为例,首先安装 protoc,下载地址:https://github.com/protocolbuffers/protobuf/releases,在这里我选择的是 protoc-3.9.1-win64.zip,我把它解压到 D:Plug 目录下,然后将 protoc-3.9.1-win64/bin 里面的 protoc.exe 加入到环境变量。加好后,可以查看对应版本,就代表安装好了。
grpc-gateway安装
grpc-gateway 的 GitHub 地址:https://github.com/grpc-ecosystem/grpc-gateway
grpc-gateway 使用完全的 Go 语言进行开发,所以安装起来也非常简单,首先需要获取相关的依赖包:
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
下载好之后,就会在你的 GOPATH/bin 目录下出现对应的三个 exe 文件
使用
编写proto文件
cd $GOPATH/src/
mkdir -p demo/protobuf
cd demo/protobuf
vim test.proto
syntax = "proto3";
package protobuf;
import "google/api/annotations.proto";
message TestRequest {
string value = 1;
string message = 2;
}
message TestResponse {
string msg = 1;
}
service Test {
rpc Echo(TestRequest) returns (TestResponse) {
option (google.api.http) = {
post: "/v1/test/echo"
body: "*"
};
}
}
然后进入 test.proto 文件同级目录下,运行 protoc 命令,生成 grpc 结构文件和 gateway 文件
protoc -ID:\Plug\protoc-3.9.1-win64\include -I. -I%GOPATH%/src -I%GOPATH%/src/github.com/grpc-
ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:. ./test.proto
protoc -ID:\Plug\protoc-3.9.1-win64\include -I. -I%GOPATH%/src -I%GOPATH%/src/github.com/grpc-
ecosystem/grpc-gateway/third_party/googleapis --go_out=plugins=grpc:. ./test.proto
最终可以看到以下文件
编写Grpc+Grpc-Gateway服务
本来 Grpc 和 Gateway 两个服务应该分开写的,为了省事,我就写在一起了,代码如下:
package main
import (
"flag"
"log"
"net"
"net/http"
pb "demo/protobuf"
gw "demo/protobuf"
"github.com/golang/glog"
"google.golang.org/grpc"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
)
type test struct {}
func (t *test) Echo(ctx context.Context, in *pb.TestRequest) (*pb.TestResponse, error) {
log.Println("request: ", in)
return &pb.TestResponse{Msg: in.Value + in.Message}, nil
}
func runGrpc() error {
lis, err := net.Listen("tcp", ":35254")
if err != nil {
log.Fatal("failed to listen: %v", err)
}
server := grpc.NewServer()
pb.RegisterTestServer(server, &test{})
log.Println("rpc服务已经开启")
go runGrpcGateway()
return server.Serve(lis)
}
func runGrpcGateway() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard,
&runtime.JSONPb{OrigName: true, EmitDefaults: true}))
opts := []grpc.DialOption{grpc.WithInsecure()}
err := gw.RegisterTestHandlerFromEndpoint(ctx, mux, ":35254", opts)
if err != nil {
return err
}
log.Println("gateway服务已经开启")
return http.ListenAndServe(":35255", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := runGrpc(); err != nil {
glog.Fatal(err)
}
}
运行这个网关程序
D:\GOProject\src\demo>go build main.go
D:\GOProject\src\demo>main
2020/03/08 19:00:57 rpc服务已经开启
2020/03/08 19:00:57 gateway服务已经开启
使用 http 的方式调用网关:
curl -X POST -k http://localhost:35255/v1/test/echo -d '{"value" : "Hello", "message" : " World" }'
返回结果:
{
"msg": "Hello World"
}
或者使用 Postman 模拟请求:
使用gateway生成swagger文档
protoc -ID:\Plug\protoc-3.9.1-win64\include -I. -I%GOPATH%/src -I%GOPATH%/src/github.com/grpc-
ecosystem/grpc-gateway/third_party/googleapis --swagger_out=logtostderr=true:. ./test.proto
你会发现 test.proto 同级目录下多了一个 test.swagger.json 文件。打开后效果如下: