在上一篇文章中,我介绍了使用官方gRPC插件和go-micro插件开发gRPC应用的方法,两者都可以正常工作。但是当两者混合在一起时,相互访问就成了一个问题。例如,使用go-micro插件生成的gRPC客户端访问基于官方gRPC插件创建的服务器时,会出现如下错误:
{"id":"go.micro.client","code":501,"status":"Not Implemented"}
经过一番探索,发现因为go-micro插件在生成代码的时候丢弃了proto定义,而且客户端API和服务端API都没有使用这个,所以可以在逻辑上自洽,但是在其他地方和gRPC服务通信框架或语言出现问题时。
这里以hello.proto为例:
syntax = "proto3";
option go_package="/proto";
package Business;
service Hello {
rpc Say (SayRequest) returns (SayResponse);
}
message SayResponse {
string Message = 1;
}
message SayRequest {
string Name = 1;
}
对于客户端代理,-gen-go-grpc 生成:
err := c.cc.Invoke(ctx, "/Business.Hello/Say", in, out, opts...)
-gen-micro 生成:
req := c.c.NewRequest(c.name, "Hello.Say", in)
可以清楚的看到go-micro生成的gRPC中的。当然this的风格还是有一些区别的,不过这不是问题,因为go-micro也对其进行了一些格式化处理,格式化代码在grpc插件中。
//grpc/.go :
func methodToGRPC(service, method string) string {
// no method or already grpc method
if len(method) == 0 || method[0] == '/' {
return method
}
// assume method is Foo.Bar
mParts := strings.Split(method, ".")
if len(mParts) != 2 {
return method
}
if len(service) == 0 {
return fmt.Sprintf("/%s/%s", mParts[0], mParts[1])
}
// return /pkg.Foo/Bar
return fmt.Sprintf("/%s.%s/%s", service, mParts[0], mParts[1])
}
可以看到go-micro直接使用了服务名作为名字。它们不可能相等,当它们不相同时就会出现问题。
网上没有人提到这个问题,可能没有多少人结合使用。于是研究了go-micro的源码,因为生成的代码缺少信息,所以要解决这个问题,就得从-gen-micro下手。
注意这里使用的是go-micro v4版本,其他版本暂未跟进。
客户端修改
针对客户端问题,我做了如下修改:
在生成客户端的时候添加,直接生成gRPC风格(go-micro内部其实是支持这种风格的),修改文件:cmd/-gen-micro//micro/micro.go
func (g *micro) generateClientMethod(pkg, reqServ, servName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) {
reqMethod := fmt.Sprintf("%s.%s", servName, method.GetName())
useGrpc := g.gen.Param["use_grpc"]
if useGrpc != "" {
reqMethod = fmt.Sprintf("/%s.%s/%s", pkg, servName, method.GetName())
}
...
因为需要预兼容,不能影响已有用户,所以在这个逻辑中加了一个开关,新的生成方式只会通过参数来应用。方法的pkg参数本来是不存在的,是新增的,从上下文中更容易获取。具体变化可以看这里:
现在如果明确只使用 gRPC 进行通信,或者如果需要与其他框架或语言的 gRPC 应用程序进行通信,则可以在生成代码时这样做:
protoc --go_out=. --micro_out=. --micro_opt=use_grpc=1 xxx.proto
关键是–==1,这个参数会传给-gen-micro,然后在上面修改后的代码中就可以得到,不管这个参数的值是什么,无论什么时候使用,生成 gRPC 样式的磁带。生成的代码现在如下所示:
req := c.c.NewRequest(c.name, "/Business.Hello/Say", in)
使用此客户端代理访问其他框架或语言开发的gRPC服务是没有问题的。当然访问go-micro的gRPC服务也没有问题。
如何获取最新版本的-gen-micro?此修改在提到 PR 后已合并到官方仓库,但尚未标记。可以这样安装:
go install go-micro.dev/v4/cmd/protoc-gen-micro@1919048c8f20
这可能不是一个好主意 修改,因为你还需要知道有这样一个参数。肯定还有其他的修改,但是因为我对go-micro了解不多,所以只选择了不会影响现有通讯方式的这一款。
服务器修改
服务端没有问题,其他框架或开发语言的gRPC客户端可以调用基于go-micro的gRPC服务。
我在开始测试的时候也遇到了问题。我先入为主的认为-gen-micro生成的服务器也有问题,所以我也提交了一个PR,然后就被打脸了。然后又看了一遍源码,发现go-micro服务器很巧妙的擦除了客户端请求中的信息,所以不管客户端传不传,服务器反正也不需要。
服务器注册逻辑在 //grpc/.go 中的方法:
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(s.rcvr).Type().Name()
...
server.serviceMap[s.name] = s
可以看到这里直接通过反射得到Type name作为服务名,什么都没有。
当收到客户端的 gRPC 请求时,go-micro 再次擦除了该请求。这个逻辑在 //grpc/grpc.go 中的方法中:
serviceName, methodName, err := mgrpc.ServiceMethod(fullMethod)
service := g.rpc.serviceMap[serviceName]
通过 mgrpc 获取服务名称时去掉名称。没有它没问题。
跑步效果
现在运行程序,尝试使用-gen-micro生成的客户端访问基于-gen-go-grpc的服务器。
暂无评论内容