笔者在开发基于客户端/服务端模式通信的插件的时候,需要用到轻量级最小包依赖的 RPC 框架,而市面上的 RPC 框架份量过于庞大,最终打包下来都是几十兆甚至上百兆,而这里面大多数功能我都用不上,于是思来想去我决定写一款属于自己的轻量级 RPC 框架 ShadowRPC ,简单易用快速接入。
网络通信基于 TCP/IP 为基础自定义应用层协议,常见的序列化/反序列化工具有 java 原生序列化、json 、kryo 、protobuf 、fst 和 hessian 等。
在不考虑跨语言的情况下,从序列化时长/序列化大小/易用性/扩展性这几方面考虑,综合性比较强的是 kryo ,但不支持跨语言,protobuf 性能最强且支持跨语言,但是使用时需要事先基于 proto 生成一个类。
最终选择 kryo 和 protobuf 两种序列化工具,使用的时候可选序列化类型,前者序列化几乎不受限制,后者支持跨语言,但是必须事先生成 proto 类型的类并使用其作为序列化工具。
高性能异步非阻塞框架非 Netty 不可了,客户端和服务端基于 Netty 开发可事半功倍。
除了基于 netty 外,有时需要更小的包依赖,所以 client 除了支持基于 netty 模块,还会开发一个无任何依赖的模块 mini-client ,打完包仅几十 kb 。
注册中心选择 zookeeper 作为服务注册和服务发现,当然如果只用单点模式的话其实是不需要注册中心的,所以 zookeeper 是可选组件。
@ShadowEntity
public class MyMessage {
@ShadowField(1)
private String content;
@ShadowField(2)
private int num;
}
如果是 protobuf 序列化方式,定义 proto 格式再用 maven 插件 protobuf-maven-plugin 生成实体
message MyMessage {
string content = 1;
int32 num = 2;
}
@ShadowInterface
public interface IHello {
String hello(String msg);
MyMessage say(MyMessage message);
}
然后编写服务实现类
@ShadowService(serviceName = "helloservice")
public class HelloService implements IHello {
@Override
public String hello(String msg) {
return "Hello,"+msg;
}
@Override
public MyMessage say(MyMessage message) {
MyMessage message1 = new MyMessage();
message1.setContent("hello received "+"("+message.getContent()+")");
message1.setNum(message.getNum()+1);
return message1;
}
}
单点启动模式如下:
ServerBuilder.newBuilder()
.serverConfig(serverConfig)
.addPackage("rpctest.hello")
.build()
.start();
使用 zk 作为注册中心集群模式启动
String ZK_URL = "localhost:2181";
ServerConfig serverConfig = new ServerConfig();
serverConfig.setGroup("DefaultGroup");
serverConfig.setPort(2023);
serverConfig.setRegistryUrl(ZK_URL);
serverConfig.setQpsStat(true); //统计 qps
serverConfig.setSerializer(SerializerEnum.KRYO.name());
ServerBuilder.newBuilder()
.serverConfig(serverConfig)
.addPackage("rpctest.hello")
.build()
.start();
ModulePool.getModule(ClientModule.class).init(new ClientConfig());
ShadowClient shadowClient = new ShadowClient("127.0.0.1",2023);
shadowClient.init();
IHello helloService = shadowClient.createRemoteProxy(IHello.class,"shadowrpc://DefaultGroup/helloservice";
MyMessage message = new MyMessage();
message.setNum(100);
message.setContent("Hello, Server!");
System.out.printf("发送请求 : %s\n",message);
MyMessage response = helloService.say(message);
System.out.printf("接收服务端消息 : %s\n",response);
使用 zk 作为服务发现负载均衡调用各个服务器
ClientConfig config = new ClientConfig();
config.setSerializer(SerializerStrategy.KRYO.name());
ModulePool.getModule(ClientModule.class).init(config);
String ZK_URL="localhost:2181";
ShadowClientGroup shadowClientGroup = new ShadowClientGroup(ZK_URL);
shadowClientGroup.init();
IHello helloService = shadowClientGroup.createRemoteProxy(IHello.class, "shadowrpc://DefaultGroup/helloservice");
List<ShadowClient> shadowClientList = shadowClientGroup.getShadowClients("DefaultGroup");
System.out.println("所有服务器: "+shadowClientList.stream().map(c-> c.getRemoteIp()+":"+c.getRemotePort()).collect(Collectors.toList()));
for(int i = 0 ;i<shadowClientList.size() * 5; i++) {
String hello = helloService.hello(i + "");
System.out.println(hello);
}
篇幅有限,所有源码见: https://github.com/Liubsyy/ShadowRPC
目前仅供学习交流使用,后续我将逐步打磨此 rpc 框架达到企业级水准。
1
toby1902 332 天前
大佬,我之前也写了一个自己用的 RPC ,不过是基于 RabbitMQ 消息队列,使用 Spring-Boot 开发的,我看了一些你的代码,欢迎交流哦,https://github.com/naivetoby/simple-rpc
|
2
runningman 332 天前
能不能把 zookeeper 换成 etcd 或者 nacos
|
3
Cambra1n 332 天前
想问下用 zk 作为服务发现有什么考量吗?这个使用场景下,Eureka 在可用性上应该更有优势吧。
|
4
liubsyy OP @runningman @Cambra1n 当然可以,后续把常用的组件都接上,通过配置可选
|
5
vok2aDe12AsWDirE 332 天前
协议的设计一般还要有魔数,我看使用的是:LengthFieldBasedFrameDecoder & MessageHandler 可以参考: https://github.com/yint-tech/sekiro-open 支持跨语言的设计建议还是使用 WebSocket 作为底层通讯,在此之上增加自己的语义。
|
7
runningman 332 天前
@liubsyy 那就挺好。
|