基于 netty+zk 开发高性能 rpc 框架 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liubsyy
V2EX    Java

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

  •  
  •   liubsyy 2024-01-28 22:44:40 +08:00 2710 次点击
    这是一个创建于 670 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    7 条回复    2024-01-29 16:20:00 +08:00
    toby1902
        1
    toby1902  
       2024-01-28 23:45:36 +08:00
    大佬,我之前也写了一个自己用的 RPC ,不过是基于 RabbitMQ 消息队列,使用 Spring-Boot 开发的,我看了一些你的代码,欢迎交流哦,https://github.com/naivetoby/simple-rpc
    runningman
        2
    runningman  
       2024-01-29 07:51:04 +08:00
    能不能把 zookeeper 换成 etcd 或者 nacos
    Cambra1n
        3
    Cambra1n  
       2024-01-29 08:40:10 +08:00
    想问下用 zk 作为服务发现有什么考量吗?这个使用场景下,Eureka 在可用性上应该更有优势吧。
    liubsyy
        4
    liubsyy  
    OP
       2024-01-29 12:33:00 +08:00
    @runningman @Cambra1n 当然可以,后续把常用的组件都接上,通过配置可选
    vok2aDe12AsWDirE
        5
    vok2aDe12AsWDirE  
       2024-01-29 15:16:31 +08:00
    协议的设计一般还要有魔数,我看使用的是:LengthFieldBasedFrameDecoder & MessageHandler 可以参考: https://github.com/yint-tech/sekiro-open 支持跨语言的设计建议还是使用 WebSocket 作为底层通讯,在此之上增加自己的语义。
    liubsyy
        6
    liubsyy  
    OP
       2024-01-29 15:43:18 +08:00
    @bytebuff 阁下也玩只狼吗,几周目了?
    runningman
        7
    runningman  
       2024-01-29 16:20:00 +08:00
    @liubsyy 那就挺好。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     876 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 22:14 PVG 06:14 LAX 14:14 JFK 17:14
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86