开源一个自研的基于 Netty 的高性能网络通信框架

Hermes
hermes是一款基于Netty的可以支持百万级别的并发连接的高性能、高度可扩展的的网络通讯框架,它参了dubbo和sofa-bolt的网络通讯模块的设计,hemers可以使用在IM、长连接等领域,它具有以下的特性:
- 私有的通讯协议 
- 可定制的编/解码器 
- 支持多种序列化机制 
- CRC校验 
- 客户端/服务端连接管理 
- 无锁建连 
- 连接的心跳和空闲检测 
- 客户端连接池 
- 自动断连和重连 
- 高效和可定制化的IO模型 
- 丰富的通信模型 
- oneway 
- twoway 
- callback 
- future 
- 支持客户端/服务端异步化编程 
- 超时控制 
- 使用SPI扩展点加载,扩展性强 
- 鉴权 
github地址:https://github.com/IndiraFinish/hermes
1.使用方式
- 同步调用 
- 异步调用 
- callBack调用 
- 单向调用 
ReqBody实现Serializable接口
- 命令处理器CommandHandler 
默认提供了GeneralCmdHandler处理通用命令,如果你的请求没有指定命令将会默认被
GeneralCmdHandler处理。你只需要注册UserProcesser即可。注册后在META-INF/services/文件夹配置扩展点实现。
- 扩展CommandHandler 
- 继承 - AbstractCommandHandler复写handleRequest和handleResponse方法处理你的命令。
- 在 - META-INF/services/文件夹配置扩展点实现。
你可以参考HeartbeatHandler和GeneralCmdHandler
2.源码设计
2.1 架构设计


2.2 协议设计

- 第1个字节是魔数,对于非本协议的包可以进行快速检测(fast-fail),不需要解码后的处理同时保证安全性。 
- 第9个bit是requst/response标志,1表示requst。 
- 第10个bit表示是单向调用还是双向调用。 
- 第11个bit表示是否示心跳包。 
- 第2个字节的剩余的5个bit代表序列化ID,目前只支持hessian序列化。 
- 第3-4字节(short)表示commnd code,表示这个包的命令类型。 
- 第5个字节表示响应的状态,以便客户端快速识别异常。 
- 第6个字节表示标志位(flags),用来标示是否开启CRC冗余校验、数据压缩等功能。 
- 第7-10字节(int)表示请求的唯一ID,用于双向通信的时候唤醒阻塞的线程。 
- 第11-14字节(int)表示数据body的长度,用于解码。 
2.2.1 协议命令

2.2.2 命令处理器设计

2.3 编解码
由于TCP的沾包和拆包问题,一般来说编解码分为以下几种方式:
- 基于分割符的协议 
- 基于定长的协议 
- 基于变长的协议 
一般来说不管使用何种的方式,编解码器都需要继承MessageToByteEncoder和ByteToMessageDecoder两个类。需要注意的是因为ByteToMessageDecoder维护了有状态的BtyeBuf累加器所有解码器是有状态的,不能使用@ChannelHandler.Sharable
在sofa-bolt的基础上bolt-extension参考了dubbo,增加了客户端和服务端的负载数据大小的校验,可以实现大包的快速失败(fast-fail)。
2.4 连接管理
基于Netty的FixedChannelPool实现客户端连接池和客户端并发控制。
服务端并发控制你可以使用连接监听器实现
[FixedChannelPool原理](https://juejin.im/post/5e9942e2f265da47d00a6776)
sofa-bolt的客户端连接池无锁建立连接的原理
- 使用的是ConcurrentHashMap的putIfAbsent保证无锁建立连接池 
- 使用FutureTask的在并发环境下Callable只执行一次的特新解决并发问题 
2.5 空闲检测和心跳
空闲检测基于Netty的IdleStateEvent进行读写的空闲检测。
- 客户端:只检测读超时,默认15秒发送一次心跳。超过3次没有收到响应,就会关闭连接并进行重连 
- 服务端:检测读写超时,默认90内没有读写,则直接关闭连接,等待重连。 
2.6 重连
在sofa-bolt的基础上,使用HashedWheelTimer实现了重连时间的指数退避操作,客户端三次更新读时间戳则立即重连,第二次重连为3s,第三次6秒,依次类推。默认尝试6次。
Netty最佳实践
- 选择最合适的Reactor模型,一般来说服务端的accept线程为1即可,worker线程一般为cpu*2 
- 保证串行化处理IO事件和业务,避免加锁操作。 
- ConcurrentHashMap的putIfAbsent(本质是CAS)。 
- 利用EventLoop执行可以保证串行化执行任务,避免了线程上下文切换。 
- Futuretask和Callable在多线程下的特性。 
- IO密集型的任务可以使直接在IO线程处理(EventLoop线程)避免线程上下文切换的耗时,CPU密集型任务,应当IO线程和业务线程隔离,释放IO线程进行read/write。灵活配置线程很重要。 
- 对于无状态的ChannelHandler应当设置为共享模式 - @ChannelHandler.Sharable,避免生成太多对象。
5.ChannelHandlerContext的ctx.write()与ctx.channel().write()方法。前者只会处理当前Handler前面的Handler,后者会从tail节点开始,处理整个ChannelPipeline。
- 在写数据的时候应当使用isWritable()方法来判断一下当前ChannelOutboundBuffer 里的写缓存水位。因为writeAndFlush是先发送到ChannelOutboundBuffer缓冲区,如果接受方窗口一直很小,或者网络拥塞很可能会导致OOM发生。 
- 能使用数组的情况不要使用散列表(Map) 
- 超时控制可以使用HashedWheelTimer。 
版权声明: 本文为 InfoQ 作者【Geek_373e87】的原创文章。
原文链接:【http://xie.infoq.cn/article/15af052490f281211aad3e20e】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
 
  
  
  
  
  
  
  
  
    
评论 (4 条评论)