gpRPC是一个基于BeetleX新版本开发的RPC通讯组件,那gp又是什么意思呢?其实就是Google Protobuf的简称。那有人会问不是有个gRPC组件了为何还要去实现一个类似的gpRPC?实现gpRPC的主要目标就在BeetleX高吞吐复用的特性之上实现一个高性能并且使用方便的PRC组件。
之前的组件测试已经表明Google Protobuf有着百万级别RPS的吞吐能力,毕竟那只是测试的代码在使用便利性上并不友好,封装gpRPC就可以实现基本接口代理的方式来操作RPC服务,在使用上就会变得非常简单,同样其性能也希望在32核的云主机上达到百万级别的吞吐能力。
组件开源地址:
//github.com/beetlex-io/BeetleX.Light.gpRPC
使用
gpRPC组件是使用Google Protobuf的功能,所以在消息定义上还是采用proto文件来描述(可以借助于Grpc.Tools组件可以在VS中方便处理描述文件).
syntax = "proto3";
option csharp_namespace = "gpRPC.Messages";
message RegisterReq {
uint32 Identifier=1;
string FirstName = 2;
string LastName = 3;
string Email = 4;
string Password=5;
string Address=6;
string City =7;
string Remark=8;
}
message RegisterResp{
uint32 Identifier=1;
bool Success = 2;
int64 Time=3;
}
message User{
string FirstName = 2;
string LastName = 3;
string Email = 4;
string Password=5;
string Address=6;
string City =7;
string Remark=8;
}
message UsersReq{
uint32 Identifier=1;
uint32 Count=2;
}
message UsersResp{
uint32 Identifier=1;
repeated User Items = 2;
bool Success=3;
string Error=4;
}
使用组件只有这个文件描述是不够的,因为要符合组件的处理规范还需要针对消息实现一些相关接口
[ProtocolObject(101u)]
public partial class RegisterReq : IIdentifier
{
}
[ProtocolObject(102u)]
public partial class RegisterResp : IIdentifier
{
}
[ProtocolObject(201u)]
public partial class UsersReq : IIdentifier
{
}
[ProtocolObject(202u)]
public partial class UsersResp : IIdentifier
{
}
public interface IUserHandler
{
Task<RegisterResp> Register(RegisterReq req);
Task<UsersResp> Users(UsersReq req);
}
每个类都需要实现IIdentifier接口和添加ProtocolObject属性标记,IIdentifier要用于维护请求响应匹配的关系,由于gpRPC支持单连接请求复用所以需要有一个ID值来维护请求和响应的关系。ProtocolObject则用于描述一个uint对应的类型用于组件匹配具体的对象类型。后面IUserHandler接口是gpRPC功能接口描述,它给客户端代理调用和服务端实现具体处理逻辑用的。
[RpcService]
public class UserHandler : IUserHandler
{
public async Task<RegisterResp> Register(RegisterReq req)
{
RegisterResp resp = new RegisterResp();
resp.Success = true;
resp.Time = DateTime.Now.Ticks;
return resp;
}
public async Task<UsersResp> Users(UsersReq req)
{
UsersResp resp = new UsersResp();
for (int i = 0; i < req.Count; i++)
{
User user = new User();
user.Address = $"guangzhouLongdong{i}";
user.City = $"guzngzhou{i}";
user.Email = "henryfan@msn.com";
user.FirstName = $"fan{i}";
user.LastName = $"henry{i}";
user.Password = "122".PadLeft(i, 'a');
user.Remark = $"{i}";
resp.Items.Add(user);
}
return resp;
}
}
以上是服务端实现的处理逻辑,实现的类需要使用RpcService标记便于服务器加载并和请求的消息匹配。
服务端
消息和逻辑定义好后就可以通过gpRPC的RpcServer对象加载启动服务
RpcServer<ApplicationBase> server = new RpcServer<ApplicationBase>();
server.Options.SetDefaultListen(o =>
{
o.Port = 8080;
});
server.RegisterMessages<RegisterReq>();
server.Options.AddLogOutputHandler<LogOutputToConsole>();
server.Options.LogLevel = LogLevel.Trace;
server.Start();
客户端
客户端使用也非常方便,通过gpRPC的RpcClient对象创建接口实例即可以进行服务调处理
RpcClient client = "tcp://localhost:8080";
client.AddLogOutputHandler<LogOutputToConsole>();
client.RegisterMessages<RegisterReq>();
client.LogLevel = LogLevel.Trace;
IUserHandler handler = client.Create<IUserHandler>();
RegisterReq req = new RegisterReq();
req.Address = $"guangzhouLongdong";
req.City = $"guzngzhou";
req.Email = "henryfan@msn.com";
req.FirstName = $"fan";
req.LastName = $"henry";
req.Password = "122";
var resp = await handler.Register(req);
注意:RpcClient和创建的接口实例都是线程安全的,可以在任意线程中重复调用。
性能
老规矩还是做一下性能测试,测试环境还是在A家云32核计算型的云主机上测。
一连接100并发
20连接2000并发
从测试结果来看20连接2000并发的量延时有些高,虽然RPS达到了110W,但发送IO竟然达到80万,这样看来消息合并利用率不高导致损耗有些高,如果能和Receive这样的利用率同样的吞吐CPU使用率会大大下降!
看来需要调整一下组件的内部机制,如果批量IO优化好处理100万RPS的远程接口调用估计50%的CPU即可,希望优化能达到我预想的目标!
BeetleX
开源跨平台通讯框架(支持TLS)
提供HTTP,Websocket,MQTT,Redis,RPC和服务网关开源组件
个人微信:henryfan128 QQ:28304340
有丰富的高吐网络服务设计经验
关注公众号
https://github.com/beetlex-io/