大家好,我是peachesTao,今天是五一假期的第4天,首先祝大家劳动节快乐。今天给大家推荐一个统一http和grpc客户端调用的库,名为prpc,github地址:prpc,该库是我公司根据最佳实践总结开发出来的,它可以提升开发效率,让开发者聚焦于业务层代码。
下面我从三个方面来介绍
编写http客户端代码痛点
首选我们来看一段调用http接口获取用户列表的代码
type GetUserListReq struct {
Age int32 `json:"age"`
}
type GetUserListReply struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
List []*UserInfo `json:"list"`
}
type UserInfo struct {
Name string `json:"name"`
Age int32 `json:"age"`
}
func GetUserList(req *protocol.GetUserListReq) (*GetUserListReply, error) {
user := config.GetServerConfig().User
request, err := httplib.Post(user.URL+user.GetUserList).Debug(true).JSONBody(req)
if err != nil {
return nil, err
}
resp := new(protocol.GetUserListReply)
err = request.ToJSON(resp)
if err != nil {
return nil, err
}
return resp, nil
}
分别定义了请求和响应数据结构体,从配置文件中获取http服务的url和用户列表接口的path,将请求结构体编码,然后通过http client库发起http请求,将返回的数据解码为响应结构体。
我们发现这样的代码有下面三个痛点
每次调用都要对req对象编码、res对象进行解码
需要在代码中硬编码读取配置服务端的url
如果服务端将Post改成Get方式,要改客户端的代码
这非常影响开发效率且容易出错,这些代码能不能自动生成?答案是可以的,凡是有规律的代码都可以自动生成。我们的实现方案是基于protobuf实现的。
基于protobuf的http客户端代码生成
我们使用protobuf定义服务调用方法、请求和响应结构体,再借助http Method Option自定义服务url和http method,最后用prpc内置的插件protoc-gen-go-prpc生成跟grpc相似的函数签名的客户端代码。
GetUserList protobuf文件定义:
syntax = "proto3";
option go_package = "/api/";
import "google/api/annotations.proto";
service User {
//获取用户列表
rpc GetUserList(GetUserListReq) returns (GetUserListReply) {
option (google.api.http) = {
get: "/user/list"
body: "*"
additional_bindings:[
{custom:{kind:"Content-Type", path:"application/json"}}]
};
}
}
message GetUserListReq {
int32 age = 1;
}
message GetUserListReply {
int32 code = 1;
string msg = 2 ;
repeated UserInfo list = 3;
}
message UserInfo {
string name = 1;
int32 age = 2;
}
自动生成的调用入口函数如下(此处省略了其他部分代码,想看完整的代码可以前往prpc-examples):
func (c *userClient) GetUserList(ctx context.Context, in *GetUserListReq, opts ...http.CallOption) (*GetUserListReply, error) {
out := new(GetUserListReply)
header := map[string]string{
"Content-Type": "application/json",
}
opts = http.CallOptions(opts).CombineHeader(header)
err := c.cc.HttpInvoke(ctx, "GET", "/user/list", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
开发者侧调用代码:
conn := prpc.NewClientConn()
err := conn.NewHttpClientConn(context.Background(), "127.0.0.1:8000")
if err != nil {
log.Error(err)
return
}
userClient := api.NewUserClient(conn)
listReply, err := userClient.GetUserList(context.Background(), &api.GetUserListReq{Age: 18})
if err != nil {
log.Error(err)
return
}
log.Info(listReply)
可以看出,开发者就像调用grpc方法一样简单的调用http接口,开发者只需要在protobuf中定义相关信息,其他的交给插件就行。
正如上面介绍的,prpc的http调用代码是根据protobuf文件自动生成的,我们知道,grpc客户端调用代码也是根据protobuf使用插件自动生成的。
这样就实现了http和grpc调用代码都统一由protobuf文件生成,这样做的好处有:
对开发者友好,因为http和grpc调用代码有相似的函数签名,无须维护两套代码
降低http和grpc切换成本,http切换到grpc只需要在protobuf文件中删除google.api.http选项,同理,grpc切换到http加上它,另外变更下初始化conntion方式(http为NewHttpClientConn,grpc为NewGrpcClientConn),调用侧代码无须更改
另外我们还在插件上做到了统一,protoc-gen-go-prpc不仅能生成http调用代码,还可以生成grpc客户端和服务端代码,无须再通过protoc-gen-go插件生成(最新的protobu改成了由protoc-gen-go-grpc生成)。
prpc还包含集群模式下的服务发现和负载均衡功能,一套服务发现和负载均衡机制同时兼容http和grpc,且都支持扩展。
开发者只需要实现resolver和balancer接口就可以实现自己的服务发现和负载均衡,目前prpc内置了基于consul的服务发现和轮训的负载均衡功能。
后续我们将增加基于zookeeper,etcd的服务发现和实现权重的负载均衡方式,也欢迎小伙伴积极参与进来,给我们贡献代码,共同完善prpc。
更多的细节请前往github prpc了解
[prpc] https://github.com/classtorch/prpc
[prpc-examples] https://github.com/classtorch/prpc-examples