基于protobuf快速生成服务治理的RPC代码
smallnest · · 1075 次点击 · · 开始浏览rpcx是一个流行的Go语言实现的服务治理的框架,只要你简单会写Go的函数,你就能实现带服务治理的RPC服务,超级简单,比如下面的加法服务:
12345
type Arith struct {}func (a *Arith) Add(ctx context.Context, args int, reply *int) error {*reply = args + 100}
但是,很多情况下,尤其在大型互联网公司中,常用Protobuf的proto文件定义数据类型(Message)和(Service),这样做有一个好处,就是方便部门和部门、服务提供者和服务使用者之间沟通,通过 IDL 文件的方式更能精准的描述服务和数据类型。
比如下面的helloworld.proto,定义一个打招呼的服务(Greeter), 这个服务使用HelloRequest和HelloResponse做请求参数和返回结果:
1234567891011121314151617
syntax = "proto3";option go_package = "helloword";package helloworld;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}}message HelloRequest {string name = 1;}message HelloReply {string message = 1;}
rpcx的用户也不止一次的提出希望rpcx也能支持从proto生成rpcx服务端和客户端的代码,现在可以宣布了,rpcxio组织提供了两个protoc的插件,可以方便的生成Go代码,和grpc插件的使用姿势一样:
- protoc-gen-rpcx:基于官方的grpc插件,生成标准的protobuf GO类型和rpcx代码
- protoc-gen-gogorpcx: 基于 gogo/protobuf的插件,可以生成性能更加优异、更多辅助代码的Go代码和rpcx代码
下面就介绍这两种工具的实现。
protoc-gen-rpcx
基于官方库golang/protobuf开发的protoc插件。
依照官方的说法(issue#1111), github.com/golang/protobuf/protoc-gen-go/grpc代码已经死了,不再维护,现在的grpc代码在golang/protobuf/internal/gengogrpc。但是这导致一个问题,官方将grpc代码放到了internal,不想其他用户扩展或者基于grpc库做开发。
同时, golang/protobuf v1.4 以及后续版本基于protobuf 新版本(APIv2,google.golang.org/protobuf)做开发,目前还在开发之中,不稳定。
基于此,我们protoc-gen-rpcx插件使用最新的golang/protobuf v1.3.5上实现,因为protoc-gen-go并没有设计成易于使用的lib方式,所以protoc-gen-rpcx采用将代码复制到golang/protobuf v1.3.5中进行编译。
具体的编译方式如下(禁用go module)。
首先, 检出protobuf v1.3.5和protoc-gen-rpcx:
123456789
export GO111MODULE=offexport GOPATH="$(go env GOPATH)"go get github.com/golang/protobuf/{proto,protoc-gen-go}export GIT_TAG="v1.3.5"git -C $GOPATH/src/github.com/golang/protobuf checkout $GIT_TAGgo get github.com/rpcxio/protoc-gen-rpcx
然后,将protoc-gen-rpcx代码复制到protobuf v1.3.5代码中:
1
cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go && cp -r $GOPATH/src/github.com/rpcxio/protoc-gen-rpcx/{link_rpcx.go, rpcx} .
接下来重新编译和安装protoc-gen-go工具,这个工具现在包含了rpcx插件,可以生成Go代码和rpcx代码。
1
go install github.com/golang/protobuf/protoc-gen-go
最后,确保protoc-gen-go工具在$PATH路径中:
1
export PATH=$PATH:$GOPATH/bin
恭喜你,现在你已经成功的生成了protoc-gen-go工具,这个工具包含官方的所有功能,可以生成proto的Go代码和grpc代码,同时它还可以生成rpcx代码:
1
protoc -I.:${GOPATH}/src --go_out=plugins=rpcx:. helloworld.proto
hello world例子
通过上面的protoc可以将helloworld.proto编译成Go代码,同时还包含rpcx server skeleton代码和rpcx client stub代码。
Server skeleton
服务端的代码只是一个骨架,提供了服务方法的定义和简单的服务启动的方法,你要做的事情就是完成服务方法的商业逻辑,以及配置你的服务器启动代码。
比如这个打招呼的例子, 客户端传入一个名称,你可以返回一个hello
它提供了一个简单启动服务的方法,你可以在此基础上实现服务端的代码,比如讲很多服务的启动代码合并到一个启动服务中,配置注册中心和其它插件等等。
12345678910111213141516171819202122232425262728
package mainimport (context "context""fmt"helloworld "github.com/golang/protobuf/protoc-gen-go/testdata/rpcx"server "github.com/smallnest/rpcx/server")func main() {s := server.NewServer()s.RegisterName("Greeter", new(GreeterImpl), "")err := s.Serve("tcp", ":8972")if err != nil {panic(err)}}type GreeterImpl struct{}// SayHello is server rpc method as definedfunc (s *GreeterImpl) SayHello(ctx context.Context, args *helloworld.HelloRequest, reply *helloworld.HelloReply) (err error) {*reply = helloworld.HelloReply{Message: fmt.Sprintf("hello %s!", args.Name),}return nil}
Client stub
客户端生成的代码更友好,它包装了XClient对象,提供了符合人工美学的方法调用格式(请求参数作为方法参数,返回结果作为方法的返回值)。并且提供了客户端的配置方式。
你也可以扩展客户端的配置,提供注册中心、路由算法,失败模式、重试、熔断等服务治理的设置。
123456789101112131415161718192021222324
package mainimport ("context""fmt"helloworld "github.com/golang/protobuf/protoc-gen-go/testdata/rpcx")func main() {xclient := helloworld.NewXClientForGreeter("127.0.0.1:8972")client := helloworld.NewGreeterClient(xclient)args := &helloworld.HelloRequest{Name: "rpcx",}reply, err := client.SayHello(context.Background(), args)if err != nil {panic(err)}fmt.Println("reply: ", reply.Message)}
protoc-gen-gogorpcx
上面的代码是基于官方的生成代码实现的,其中数据类型(Message)的生成非常的正统,而实际GO生态圈使用更多的是gogo/protobuf生成库,相比较官方库,它可以生成强大的代码:
- 更快的序列化和反序列化方法
- 更规范的Go数据结构
- 兼容 go protobuf
- 非常多的辅助方法
- 可以产生测试代码和benchmark代码
- 其它序列化格式
在etcd、cockroachdb、k8s、dgraph、tidb等项目中都有应用。所以我们也提供了基于gogo/protobuf的rpcx插件。
根据性能和功能的不同,分为四个插件:
- protoc-gen-gofast (在gofast一基础上, 可以导入gogoprotobuf)
- protoc-gen-gogofast (在gofast一基础上, 可以导入gogoprotobuf)
- protoc-gen-gogofaster (在gogofast基础上, 去掉XXX_unrecognized方法, 更少的指针字段)
- protoc-gen-gogoslick (在gogofaster基础上, 辅助方法string, gostring 和 equal)
在代码生成中,你可以根据场景选择不同的插件,不用担心对方使用何种方式解析protobuf,因为生成的字节流是符合protobuf规范的:
protoc --gofast_out=plugins=rpcx:. helloworld.protoprotoc --gogofast_out=plugins=rpcx:. helloworld.protoprotoc --gogofaster_out=plugins=rpcx:. helloworld.protoprotoc --gogoslick_out=plugins=rpcx:. helloworld.proto
生成的rpcx代码和上面的插件是一样的,所以代码例子也一样。
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
rpcx是一个流行的Go语言实现的服务治理的框架,只要你简单会写Go的函数,你就能实现带服务治理的RPC服务,超级简单,比如下面的加法服务:
12345
type Arith struct {}func (a *Arith) Add(ctx context.Context, args int, reply *int) error {*reply = args + 100}
但是,很多情况下,尤其在大型互联网公司中,常用Protobuf的proto文件定义数据类型(Message)和(Service),这样做有一个好处,就是方便部门和部门、服务提供者和服务使用者之间沟通,通过 IDL 文件的方式更能精准的描述服务和数据类型。
比如下面的helloworld.proto,定义一个打招呼的服务(Greeter), 这个服务使用HelloRequest和HelloResponse做请求参数和返回结果:
1234567891011121314151617
syntax = "proto3";option go_package = "helloword";package helloworld;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}}message HelloRequest {string name = 1;}message HelloReply {string message = 1;}
rpcx的用户也不止一次的提出希望rpcx也能支持从proto生成rpcx服务端和客户端的代码,现在可以宣布了,rpcxio组织提供了两个protoc的插件,可以方便的生成Go代码,和grpc插件的使用姿势一样:
- protoc-gen-rpcx:基于官方的grpc插件,生成标准的protobuf GO类型和rpcx代码
- protoc-gen-gogorpcx: 基于 gogo/protobuf的插件,可以生成性能更加优异、更多辅助代码的Go代码和rpcx代码
下面就介绍这两种工具的实现。
protoc-gen-rpcx
基于官方库golang/protobuf开发的protoc插件。
依照官方的说法(issue#1111), github.com/golang/protobuf/protoc-gen-go/grpc代码已经死了,不再维护,现在的grpc代码在golang/protobuf/internal/gengogrpc。但是这导致一个问题,官方将grpc代码放到了internal,不想其他用户扩展或者基于grpc库做开发。
同时, golang/protobuf v1.4 以及后续版本基于protobuf 新版本(APIv2,google.golang.org/protobuf)做开发,目前还在开发之中,不稳定。
基于此,我们protoc-gen-rpcx插件使用最新的golang/protobuf v1.3.5上实现,因为protoc-gen-go并没有设计成易于使用的lib方式,所以protoc-gen-rpcx采用将代码复制到golang/protobuf v1.3.5中进行编译。
具体的编译方式如下(禁用go module)。
首先, 检出protobuf v1.3.5和protoc-gen-rpcx:
123456789
export GO111MODULE=offexport GOPATH="$(go env GOPATH)"go get github.com/golang/protobuf/{proto,protoc-gen-go}export GIT_TAG="v1.3.5"git -C $GOPATH/src/github.com/golang/protobuf checkout $GIT_TAGgo get github.com/rpcxio/protoc-gen-rpcx
然后,将protoc-gen-rpcx代码复制到protobuf v1.3.5代码中:
1
cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go && cp -r $GOPATH/src/github.com/rpcxio/protoc-gen-rpcx/{link_rpcx.go, rpcx} .
接下来重新编译和安装protoc-gen-go工具,这个工具现在包含了rpcx插件,可以生成Go代码和rpcx代码。
1
go install github.com/golang/protobuf/protoc-gen-go
最后,确保protoc-gen-go工具在$PATH路径中:
1
export PATH=$PATH:$GOPATH/bin
恭喜你,现在你已经成功的生成了protoc-gen-go工具,这个工具包含官方的所有功能,可以生成proto的Go代码和grpc代码,同时它还可以生成rpcx代码:
1
protoc -I.:${GOPATH}/src --go_out=plugins=rpcx:. helloworld.proto
hello world例子
通过上面的protoc可以将helloworld.proto编译成Go代码,同时还包含rpcx server skeleton代码和rpcx client stub代码。
Server skeleton
服务端的代码只是一个骨架,提供了服务方法的定义和简单的服务启动的方法,你要做的事情就是完成服务方法的商业逻辑,以及配置你的服务器启动代码。
比如这个打招呼的例子, 客户端传入一个名称,你可以返回一个hello
它提供了一个简单启动服务的方法,你可以在此基础上实现服务端的代码,比如讲很多服务的启动代码合并到一个启动服务中,配置注册中心和其它插件等等。
12345678910111213141516171819202122232425262728
package mainimport (context "context""fmt"helloworld "github.com/golang/protobuf/protoc-gen-go/testdata/rpcx"server "github.com/smallnest/rpcx/server")func main() {s := server.NewServer()s.RegisterName("Greeter", new(GreeterImpl), "")err := s.Serve("tcp", ":8972")if err != nil {panic(err)}}type GreeterImpl struct{}// SayHello is server rpc method as definedfunc (s *GreeterImpl) SayHello(ctx context.Context, args *helloworld.HelloRequest, reply *helloworld.HelloReply) (err error) {*reply = helloworld.HelloReply{Message: fmt.Sprintf("hello %s!", args.Name),}return nil}
Client stub
客户端生成的代码更友好,它包装了XClient对象,提供了符合人工美学的方法调用格式(请求参数作为方法参数,返回结果作为方法的返回值)。并且提供了客户端的配置方式。
你也可以扩展客户端的配置,提供注册中心、路由算法,失败模式、重试、熔断等服务治理的设置。
123456789101112131415161718192021222324
package mainimport ("context""fmt"helloworld "github.com/golang/protobuf/protoc-gen-go/testdata/rpcx")func main() {xclient := helloworld.NewXClientForGreeter("127.0.0.1:8972")client := helloworld.NewGreeterClient(xclient)args := &helloworld.HelloRequest{Name: "rpcx",}reply, err := client.SayHello(context.Background(), args)if err != nil {panic(err)}fmt.Println("reply: ", reply.Message)}
protoc-gen-gogorpcx
上面的代码是基于官方的生成代码实现的,其中数据类型(Message)的生成非常的正统,而实际GO生态圈使用更多的是gogo/protobuf生成库,相比较官方库,它可以生成强大的代码:
- 更快的序列化和反序列化方法
- 更规范的Go数据结构
- 兼容 go protobuf
- 非常多的辅助方法
- 可以产生测试代码和benchmark代码
- 其它序列化格式
在etcd、cockroachdb、k8s、dgraph、tidb等项目中都有应用。所以我们也提供了基于gogo/protobuf的rpcx插件。
根据性能和功能的不同,分为四个插件:
- protoc-gen-gofast (在gofast一基础上, 可以导入gogoprotobuf)
- protoc-gen-gogofast (在gofast一基础上, 可以导入gogoprotobuf)
- protoc-gen-gogofaster (在gogofast基础上, 去掉XXX_unrecognized方法, 更少的指针字段)
- protoc-gen-gogoslick (在gogofaster基础上, 辅助方法string, gostring 和 equal)
在代码生成中,你可以根据场景选择不同的插件,不用担心对方使用何种方式解析protobuf,因为生成的字节流是符合protobuf规范的:
protoc --gofast_out=plugins=rpcx:. helloworld.protoprotoc --gogofast_out=plugins=rpcx:. helloworld.protoprotoc --gogofaster_out=plugins=rpcx:. helloworld.protoprotoc --gogoslick_out=plugins=rpcx:. helloworld.proto
生成的rpcx代码和上面的插件是一样的,所以代码例子也一样。