毫无疑问,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的实现方式。