解决go-micro与其它gRPC框架之间的通信问题

在上一篇文章中,我介绍了使用官方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的服务器。

© 版权声明
THE END
喜欢就支持一下吧
点赞12赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容