Protobuf与GRPC
安装
https://github.com/protocolbuffers/protobuf/releases
已经有构建好的运行文件,可以下载压缩包在bin/目录下找到,如果想自己构建请访问https://github.com/protocolbuffers/protobuf/blob/main/src/README.md
安装完成后运行protoc --version查看是否成功。
protoc作用是将.proto文件转换,至于转换成什么语言需要我们指定参数,比如Go语言需要指定–go_out ,指定完参数我们还不能实现proto->go的转换,因为我们还缺少protoc-gen-go运行文件
旧版本:运行go install github.com/golang/protobuf/protoc-gen-go对protoc-gen-go进行安装,当然也可以下载源码手动构建(go get -u 就是这么做的)
新版本:go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go
注意,protobuf有两个重要的版本区别,旧版本github.com/golang/protobuf/protoc-gen-go 它带有protoc-gen-go,它生成protobuf消息的序列化和 grpc代码(使用–go_out=plugins=grpc时)
新版本 google.golang.org/protobuf/ 不再支持生成 gRPC 服务定义,如果想要生成grpc代码需要使用新插件protoc-gen-go-grpc。
plugins参数 是旧版本,新版本已弃用。新版本方法是 --go-grpc_out=。这里我们使用新版本。
为什么要安装protoc-gen-go那?因为protoc没有内置go生成器,所以在生成时会去系统bin/中查询protoc-gen-go,然后使用protoc-gen-go生成go文件。
语法
使用
注意 --I可以指定.proto依赖包,如果在.proto文件中使用import的话。
protoc --go_out=:./ *.proto
protoc --go-grpc_out=. *.proto
该指令意思是,生成go代码。
第二行生成与grpc相关操作的代码
注意:如果预见helloworld.proto:23:1: Import “google/api/annotations.proto” was not found or had errors.这种错误,请下载google/api包
url-> grpc
protoc采用插件式所以,有很多插件可以方便我们的工作,下面讲解两个插件的使用
gateway
gateway的作用是将restful与grpc请求建立映射关系,让用户可以通过restful调用grpc
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
protoc -I . --grpc-gateway_out . --grpc-gateway_opt logtostderr=true helloworld.proto
使用-i是因为 .proto文件中使用了import “google/api/annotations.proto”;
-I的作用是指定要搜索的目录,并且可以指定多个。
syntax = "proto3";
//文件输出路径一定要有/否则报错
option go_package = "./helloworld";
package helloworld;
//引入依赖包
import "google/api/annotations.proto";
// 接口
service Greeter {
// 方法
rpc SayHello (HelloRequest) returns (HelloReply) {
//配置gateway
option (google.api.http) = {
get:"/v1/examples/sayhello"
};
}
}
// 类
message HelloRequest {
string name = 1;
}
// 类
message HelloReply {
string message = 1;
}
swagger
swagger这个没什么好说的,懂的都懂
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
protoc --openapiv2_out . --openapiv2_opt logtostderr=true *.proto
如果go get 安装不行就需要手动源码安装https://github.com/grpc-ecosystem/grpc-gateway/
会自动生成一个swagger.json文件。
注意:在运行时可能报找不到*.protoc文件一般是annotations.proto等,请自行下载文件放入目录中。
有啥用?
那么对于生成的文件我们应该怎么用,他们每个文件又有什么含义那?
首先*.pb.go 为纯go语言生成类与方法(这里有版本区别,新版本意思不变,旧版本的话如果配置了grpc插件会将grpc相关的操作也会在这个文件生成)。
那么对于新版本来说 类文件与grpc操作分开存放,*_grpc.pb.go为grpc操作的文件。
我们在使用的时候,如果某些功能不建议使用grpc的情况下该怎么办那,这时候gateway就出厂了,gateway作用是使用ServeMux将grpc操作封装成restful接口。
Grpc
上面我们讲解了protoc如何生成文件,以及每个文件有什么用,接下来我们将讲解GRPC使用方法。
在https://github.com/grpc/grpc-go.git examples目录下有grpc的测试DEMO
接下来让我们使用官方实例Helloworld进行代码讲解
客户端
package main
import (
"context"
"flag"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// 创建grpc连接,第一参数为服务器地址,第二个参数是配置
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
// 封装成GreeterClient,目的是达到调用方法就能发送请求的目的。(嗯本质就是客户端服务器呗RPC也是)。
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
// 调用方法,请求,原理是使用上面创建的conn发送请求。这里需要注意传入的类时我们通过proto生成的类,
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
}
客户端配置
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
// 第二个参数为配置参数
//我们不需要传入整个Option类
//GRPC给我们封装成了方法形式代用配置,
//具体方法可以根据下面的配置类转换(将首字母转换成大写,智能提示应该就可以提示出来)
type dialOptions struct {
// 拦截器,在invoke方法中调用
/*
if cc.dopts.unaryInt != nil {
return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...)
}
return invoke(ctx, method, args, reply, cc, opts...)
*/
unaryInt UnaryClientInterceptor
// 流拦截器
streamInt StreamClientInterceptor
// 拦截链
chainUnaryInts []UnaryClientInterceptor
// 拦截链
chainStreamInts []StreamClientInterceptor
// 定义GRPC失败策略
bs internalbackoff.Strategy
// 拨号阻塞
block bool
// 超时时间
timeout time.Duration
// 连接方式 默认是ServerName
authority string
// transport.ConnectOptions
copts transport.ConnectOptions
// 前后置处理器
callOptions []CallOption
// channelz数据库标识
channelzParentID *channelz.Identifier
//
disableServiceConfig bool
//
disableRetry bool
//
disableHealthCheck bool
//
healthCheckFunc internal.HealthChecker
//
minConnectTimeout func() time.Duration
//
resolvers []resolver.Builder
}
服务端
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// 我们需要实现接口类,但是这里并没有,而是继承了UnimplementedGreeterServer类,目的是可以不实现接口的所有方法,而另一个目的,在生成时UnimplementedGreeterServer类实现了SayHello方法,其功能就是返回没有实现当前方法信息,提醒我们需要去主动重构。
type server struct {
pb.UnimplementedGreeterServer
}
// 实现方法,返回值使用生成的类
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
flag.Parse()
// 创建监听事件
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 创建grpc服务器
s := grpc.NewServer()
// 注册grpc服务器,将请求服务名与方法绑定
// 这里是 服务名helloworld.Greeter,方法 SayHello
// 客户端在请求的时候就是根据服务名进行请求的。
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
// 启动监听事件
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
在使用过程中,proto插件已经将我们需要的grpc操作生成完成,我们只需要对其进行调用即可。
服务端配置
下面我们讲一下创建GRPC时可以进行的配置
// NewServer(opt ...ServerOption)
s := grpc.NewServer()
//但是我们不需要传入整个ServerOption类
//GRPC给我们封装成了方法形式代用配置,例如 NewServer(grpc.WriteBufferSize(1))
//具体方法可以根据下面的配置类转换(将首字母转换成大写,智能提示应该就可以提示出来)
type serverOptions struct {
// 定义协议支持,比如TLS、SSL
creds credentials.TransportCredentials
//包含了Codec and encoding.Codec
codec baseCodec
/*拦截器,在即将调用SayHello方法时调用,
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)*/
unaryInt UnaryServerInterceptor
//拦截流器
streamInt StreamServerInterceptor
//拦截链,GRPC内部会将其处理成链
chainUnaryInts []UnaryServerInterceptor
//拦截链,GRPC内部会将其处理成链
chainStreamInts []StreamServerInterceptor
//
inTapHandle tap.ServerInHandle
//处理连接接口
statsHandler stats.Handler
//限制最大并发流 通过MaxConcurrentStreams(n uint32)设置
maxConcurrentStreams uint32
// 默认1024 * 1024 * 4 最大接收消息大小
maxReceiveMessageSize int
// 默认math.MaxInt32 最大发送消息大小
maxSendMessageSize int
// 设置一个404处理
unknownStreamDesc *StreamDesc
//服务器设置 keepalive 和 max-age 参数
keepaliveParams keepalive.ServerParameters
//keepalive 强制策略
keepalivePolicy keepalive.EnforcementPolicy
// 流设置窗口大小 窗口大小的下限是 64K,任何小于该值的值都将被忽略。
initialWindowSize int32
//连接窗口大小 窗口大小的下限是 64K,任何小于该值的值都将被忽略。
initialConnWindowSize int32
//默认32 * 1024 写缓存
writeBufferSize int
//默认32 * 1024 读缓存
readBufferSize int
// 连接超时时间 默认120 * time.Second
connectionTimeout time.Duration
maxHeaderListSize *uint32
headerTableSize *uint32
// 设置处理传入流的并发数 设置0则每个都创建一个并发
numServerWorkers uint32
}
Grpc gateway
我们生成了gateway那么应该怎么使用它那?
ctx := context.Background()
mux := http.NewServeMux()
muxgw := runtime.NewServeMux()
// http到grpc映射,添加到muxgw中
helloworld.RegisterGreeterHandlerServer(ctx, muxgw, &server{})
mux.Handle("/", muxgw)
mux.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("fewafa"))
},
)
fmt.Println("开始监听")
http.ListenAndServe("localhost:8090", mux)
我们请求http://localhost:8090/v1/examples/sayhello查看是否返回正确的信息