
笔者在开发基于客户端/服务端模式通信的插件的时候,需要用到轻量级最小包依赖的 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 2024-01-28 23:45:36 +08:00 大佬,我之前也写了一个自己用的 RPC ,不过是基于 RabbitMQ 消息队列,使用 Spring-Boot 开发的,我看了一些你的代码,欢迎交流哦,https://github.com/naivetoby/simple-rpc |
2 runningman 2024-01-29 07:51:04 +08:00 能不能把 zookeeper 换成 etcd 或者 nacos |
3 Cambra1n 2024-01-29 08:40:10 +08:00 想问下用 zk 作为服务发现有什么考量吗?这个使用场景下,Eureka 在可用性上应该更有优势吧。 |
4 liubsyy OP @runningman @Cambra1n 当然可以,后续把常用的组件都接上,通过配置可选 |
5 vok2aDe12AsWDirE 2024-01-29 15:16:31 +08:00 协议的设计一般还要有魔数,我看使用的是:LengthFieldBasedFrameDecoder & MessageHandler 可以参考: https://github.com/yint-tech/sekiro-open 支持跨语言的设计建议还是使用 WebSocket 作为底层通讯,在此之上增加自己的语义。 |
7 runningman 2024-01-29 16:20:00 +08:00 @liubsyy 那就挺好。 |