基于 netty+zk 开发高性能 rpc 框架

106 天前
 liubsyy

笔者在开发基于客户端/服务端模式通信的插件的时候,需要用到轻量级最小包依赖的 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 是可选组件。

快速使用

1.定义实体作为序列化的对象(可选)

@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;
}

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;
    }
}

3.最后指定序列化类型和端口,启动服务端

单点启动模式如下:

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();

4.客户端调用 rpc 服务

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 框架达到企业级水准。

1517 次点击
所在节点    Java
7 条回复
toby1902
106 天前
大佬,我之前也写了一个自己用的 RPC ,不过是基于 RabbitMQ 消息队列,使用 Spring-Boot 开发的,我看了一些你的代码,欢迎交流哦,https://github.com/naivetoby/simple-rpc
runningman
106 天前
能不能把 zookeeper 换成 etcd 或者 nacos
Cambra1n
106 天前
想问下用 zk 作为服务发现有什么考量吗?这个使用场景下,Eureka 在可用性上应该更有优势吧。
liubsyy
106 天前
@runningman @Cambra1n 当然可以,后续把常用的组件都接上,通过配置可选
bytebuff
105 天前
协议的设计一般还要有魔数,我看使用的是:LengthFieldBasedFrameDecoder & MessageHandler 可以参考: https://github.com/yint-tech/sekiro-open 支持跨语言的设计建议还是使用 WebSocket 作为底层通讯,在此之上增加自己的语义。
liubsyy
105 天前
@bytebuff 阁下也玩只狼吗,几周目了?
runningman
105 天前
@liubsyy 那就挺好。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1012256

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX