rpcx 官方技术博客

rpcx 支持 grpc 啦!

2021.11.09

毫无疑问,rpcx是当前最流行的Go生态圈的微服务框架之一,很多大厂和创业公司都在使用,并且服务端无需额外配置,rpcx也同时支持HTTP调用,这样其它编程语言都可以调用rpcx服务,但是大家还是一直念叨,能不能通过grpc调用rpcx服务,因为毕竟,很多大厂中很多使用Java、C++的同学,比较熟悉grpc,一时半会很难转换到Go生态圈中来。

虽然,grpc的服务和rpcx的服务都很类似,需要请求(Request)和响应(Response),并且第一个参数页都是Context,但是rpcx的服务签名风格是和Go标准库中的rpc库的风格类似,响应(Response)是作为返回值返回的,也就是说,两者的服务不能直接拿回来使用,需要做一点点小小的转换。

从实践的角度,你可以实现rpcx的服务(通过自动生成插件protoc-gen-gogorpcx或者protoc-gen-rpcx,可以从proto文件生成rpcx服务),然后基于此实现grpc的服务。 也可以先生成grpc服务,再封装成rpcx的服务。两种方式都可以,这样就可以保证逻辑代码只有一处实现。

除了服务端支持grpc, rpcx客户端也要支持访问grpc服务,这样才算完全支持。

现在, rpcx客户端和服务端都支持grpc了。相关的插件可以查看github.com/rpcxio/rpcxplus

服务端

我们以已经有了grpc实现为例,看看rpcx服务端如何支持grpc。

grpc-go仓库中有一个hello world的例子,它的proto定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
syntax = "proto3";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

而且相关的代码已经生成好了helloworld.pb.go

我们可以基于它的数据结构定义一个grpc的服务GreeterService,它既实现了grpc服务(SayHello),又实现了grpc服务(Greet),它们的实现逻辑都是类似的,本来可以抽取成一个公共的方法,这里简单的分别实现了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type GreeterService struct {
	helloworld.UnimplementedGreeterServer
}

func (*GreeterService) SayHello(ctx context.Context, req *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
	reply := &helloworld.HelloReply{Message: "hello " + req.GetName()}
	return reply, nil
}

func (*GreeterService) Greet(ctx context.Context, req *helloworld.HelloRequest, reply *helloworld.HelloReply) error {
	*reply = helloworld.HelloReply{Message: "hello " + req.Name}
	return nil
}

接下来启动一个rpcx服务,并且配置grpc插件:

1
2
3
4
5
    s := server.NewServer()
	gs := NewGrpcServerPlugin()
	s.Plugins.Add(gs)

	greetService := &GreeterService{}

接着注册rpcx服务和grpc服务。

1
2
3
4
5
6
7
	// register rpcx service
	err := s.Register(greetService, "")
	assert.NoError(t, err)
	// register grpc service
	gs.RegisterService(func(grpcServer *grpc.Server) {
		helloworld.RegisterGreeterServer(grpcServer, greetService)
	})

最后启动服务端:

1
    s.Serve("tcp", "127.0.0.1:8972")

这时候你可以使用任意的grpc客户端访问SayHello服务,也可以使用rpcx的客户端访问Greet服务。所有的访问使用同一个地址和端口。

比如下面的测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	conn, err := grpc.Dial(s.Address().String(), grpc.WithInsecure(), grpc.WithTimeout(time.Second))
	assert.NoError(t, err)

	defer conn.Close()
	c := helloworld.NewGreeterClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &helloworld.HelloRequest{Name: "smallnest"})
	assert.NoError(t, err)
	assert.Equal(t, "hello smallnest", r.Message)

rpcx客户端

你可以按照rpcx的方式访问grpc服务,无论这个grpc服务是rpcx提供的,还是其它编程语言和框架提供的。

重要的是,使用GrpcClientPlugin插件生成rpcx的客户端。

1
2
3
    gcp := NewGrpcClientPlugin([]grpc.DialOption{grpc.WithInsecure()}, nil)

	rpcxClient, err := gcp.GenerateClient(fmt.Sprintf("grpc@%s", lis.Addr().String()), "helloworld.Greeter", "SayHello")

GrpcClientPlugin插件用来配置grpc的参数,调用它的GenerateClient方法,并且提供地址、服务名和服务方法,就可以生成rpcx的Client了。

接下来大家都熟悉了,正常的rpcx调用方式:

1
2
3
4
5
6
7
	var argv = &helloworld.HelloRequest{
		Name: "smallnest",
	}
	var reply = &helloworld.HelloReply{}
	err = rpcxClient.Call(context.Background(), "helloworld.Greeter", "SayHello", argv, reply)
	assert.NoError(t, err)
	assert.Equal(t, "hello smallnest", reply.Message)

如果想使用rpcx的客户端服务治理的功能,更常用的方式是生成XClient客户端,使用起来更简单,更符合rpcx的习惯。 重要的是配置GrpcClientPlugin插件即可,页就两行代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
	// 注册 GrpcClientPlugin 插件
	gcp := NewGrpcClientPlugin([]grpc.DialOption{grpc.WithInsecure()}, nil)
	client.RegisterCacheClientBuilder("grpc", gcp)

	// 以下是rpcx的正常调用方式
	d, _ := client.NewPeer2PeerDiscovery("grpc@"+lis.Addr().String(), "")
	opt := client.DefaultOption
	xclient := client.NewXClient("helloworld.Greeter", client.Failtry, client.RandomSelect, d, opt)


	argv := &helloworld.HelloRequest{
		Name: "smallnest",
	}
	reply := &helloworld.HelloReply{}
	err = xclient.Call(context.Background(), "SayHello", argv, reply)
	assert.NoError(t, err)
	assert.Equal(t, "hello smallnest", reply.Message)

总结

rpcx无论服务端还是客户端,支持grpc都很简单,因为主要的繁琐的处理rpcx自己已经实现了。rpcx遵循Go的less is more的设计哲学,对外提供的功能非常简单易用。

事实上,我在百度内部也实现了对百度的brpc微服务框架的支持,和grpc的实现方式。