9

基于protobuf快速生成服务治理的RPC代码

 4 years ago
source link: https://colobu.com/2020/05/10/generate-rpcx-code-from-protobuf-files/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

rpcx 是一个流行的Go语言实现的服务治理的框架,只要你简单会写Go的函数,你就能实现带服务治理的RPC服务,超级简单,比如下面的加法服务:

type Arith struct {}

func (a *Arith) Add(ctx context.Context, args int, reply *int) error {
    *reply = args +100
}

但是,很多情况下,尤其在大型互联网公司中,常用 Protobufproto 文件定义数据类型( Message )和( Service ),这样做有一个好处,就是方便部门和部门、服务提供者和服务使用者之间沟通,通过 IDL 文件的方式更能精准的描述服务和数据类型。

比如下面的 helloworld.proto ,定义一个打招呼的服务( Greeter ), 这个服务使用 HelloRequestHelloResponse 做请求参数和返回结果:

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:

export GO111MODULE=off
export 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_TAG

go get github.com/rpcxio/protoc-gen-rpcx

然后,将protoc-gen-rpcx代码复制到protobuf v1.3.5代码中:

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代码。

go install github.com/golang/protobuf/protoc-gen-go

最后,确保protoc-gen-go工具在 $PATH 路径中:

export PATH=$PATH:$GOPATH/bin

恭喜你,现在你已经成功的生成了protoc-gen-go工具,这个工具包含官方的所有功能,可以生成proto的Go代码和grpc代码,同时它还可以生成rpcx代码:

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 的字符串。

它提供了一个简单启动服务的方法,你可以在此基础上实现服务端的代码,比如讲很多服务的启动代码合并到一个启动服务中,配置注册中心和其它插件等等。

package main

import (
	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 defined
func (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对象,提供了符合人工美学的方法调用格式(请求参数作为方法参数,返回结果作为方法的返回值)。并且提供了客户端的配置方式。

你也可以扩展客户端的配置,提供注册中心、路由算法,失败模式、重试、熔断等服务治理的设置。

package main

import (
	"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.proto
protoc --gogofast_out=plugins=rpcx:. helloworld.proto
protoc --gogofaster_out=plugins=rpcx:. helloworld.proto
protoc --gogoslick_out=plugins=rpcx:. helloworld.proto

生成的rpcx代码和上面的插件是一样的,所以代码例子也一样。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK