Netty:
1、Netty三种IO
2、Netty和Reactor的
1、Netty对Reactor的支持
Netty的线程模型时基于Reactor模型实现的,Netty对Reactor三种模式都有非常好的支持,并做了一定的改善,一般情况下,在服务端会采用主从架构模型。
tips:Netty是解决网络通信的,业务线程池不归Netty管
流程:所有的客户端连接首先注册到Boss EventLoopGroup (Main Reactor),Boss EventLoopGroup不断轮询查询出Accept的客户端,交给Work EventLoopGroup,在Work EventLoopGroup中也在不断的轮询找出就绪的Read/Write事件交由pipeline来处理。(一个Socket Channel对应一个Pipeline,而一个pipeline中有多个handler来处理业务)
对于使用者来说只需要编写Handler即可,其他的主从Reactor模型Netty已经帮我们完成了
头和尾是Netty 初始化好的,因为第一步从Socket中读取数据相对来说是固定的代码,所以Netty替我们完成了,至于拿到数据后具体做什么业务,这个只能我们自己决定。所以Netty替我们初始化了一个Head handler;当我们业务完成后需要把数据写回去,这个代码也相对是固定的所以Netty通过Head Handler也完成了。至于Tail Handler则是做一些收尾工作,例如释放资源。
2、Netty中的ChannelHandler
Handler可以大致分为两类 inbound(入站)和outbound(出站)两种
- ChannelInboudHandler:入站处理器
- ChannelOutBoundHandler:出站处理器
- ChanelHandlerAdapter:提供了一些默认的实现,减少用户对于ChannelHandler的编写
- ChannelDuplexHandler:混合型,既可以处理出站也可以处理入站
3、Netty如何使用Reactor模型
Netty通过创建服务的时候配置不同的eventGroup来确认使用哪种Reactor模型
Reactor 单线程模式 | EventLoopGroup group = new NioEventLoopGroup(1); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(group); |
---|---|
非主从Reactor多线程模式 | EventLoopGroup group = new NioEventLoopGroup(1); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(group); |
主从Reactor多线程模式 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); ServerBootstrap serverBootstrap = new ServerBootstrap(); EventLoopGroup workGroup = new NioEventLoopGroup(); serverBootstrap.group(bossGroup,workGroup); |
4、Netty的核心组件
1、Bootstrap:引导类,用于配置整个Netty程序吗,将各个组件串起来,最后绑定端口启动Netty服务,Bootstrap分为两类,一种用于客户端的Bootstrap一种用于服务端的ServerBootstrap,两者的区别在于
(1)ServerBootstrap将绑定到一个端口,服务端必须要监听连接,而Bootstrap则是由想要连接的远程节点的客户端应用程序所使用。
(2)引导一个客户端只要一个EventLoopGroup,但是一个ServerBootstrap则需要两个
2、Channel
3、EventLoopGroup和EventLoop
4、ByteBuf
ByteBuf是Netty对NIO中 ByteBuffer的封装
对于直接缓冲区来说由于不受JVM的控制,所以基于Netty编写的程序如果没有处理好这部分逻辑,容易出现内存泄漏。同时,由于是向操作系统申请内存,操作系统有时候可能不会及时响应所以Netty是基于池化技术来做的。首先Netty再启动的时候会向操作系统申请一片内存,之后的操作都是基于这一片内存,从而不需要每次都进行分配内存和释放内存。
Netty默认使用了PooledByteBufAllocator,但是可以通过引导类设置非池化模式。
那么如何解决堆外内存无法被JVM管理从而无法释放的问题呢?手动释放(用完后归还给Netty内存池,注意是Release),Netty自身引入了应用技术,提供了ReferenceCounted接口,当引用计数=0时,会被释放(类似于JVM中的引用计数法)
手动释放的场景,例如现在有N多个Handler,其中一个handler的作用是把ByteBuf中的数据序列化成User对象,之后往后传的是User对象,而不是BtyeBuf了,此时就需要手动将其释放掉。
小结:
5、Future和Promise
Netty中的future 继承于JDK中的future,只不过在其基础上进行了扩展。例如添加监听器
Promise是基于netty中的 Future扩展而来, Netty中的future只增加了监听器,整个异步的状态是不能设置和修改的,于是netty的Promise接口扩展了netty的Future接口,可以设置异步执行结果。在IO操作过程中,如果顺利完成、或者发送异常,都可以设置Promise的结果,并通知给Promise的Listener们。
简单的来说,通过Promise机制,运行用户人为的控制异步任务中什么时候触发“回调函数”。
案例:
3、粘包和半粘包
问题描述,例如现在客户端想发给服务端2个包 ABC 和DEF, 为了提升性能TCP连接可能会将这两个包一起发送给服务端(二进制),变成ABCDEF(其实是二进制),此时服务端不知道到底是发了ABC和DEF还是发了ABCDEF
解决方案
既然已经知道问题了,所以只要解决数据的边界问题,即可解决TCP粘包问题。
推荐使用第三种,包括dubbo协议也是采用第三种模式,dubbo协议前16位固定写一些信息(例如协议版本、消息长度等等),后面body里才存放数据
那么Netty中是如何解决战报问题的呢?Netty提供了针对封装成帧这种形式的不同方式的拆包器,所谓的拆包器其实就是解码(说白了,就是根据某个规则来读取数据,将网络中的原始数据解析成应用层可以用的数据)。
1、固定长度不需要编码,实现比较简单
2、分隔符,即服务端和客户端约定一个分隔符。比如以#作为结束符
3、固定长度字段存消息(用的最多),固定一部分长度作为标识,可以携带整体信息,例如 协议版本号、内容的长度等等信息。比如现在一个包固定长度为204,前4个字节记录这个包的内容有多少,剩下200个字节用来存具体要传的数据。
基于长度的域解码器有4个比较重要的信息
(1)lengthFieldOffset:length域的偏移量,默认情况下从0开始读取,但是根据实际情况可以设置偏移量,比如从第2个字节开始读取。
例如:通过TCP 服务端接受到数据包 10010101,正常情况下是从低0位开始读取,如果设置了偏移量为2,则从第2个位置开始读取。
(2)lengthFieldLength:length域占用的字节数。比如10,表示前10个字节是length域
(3)lengthAdjustment:在length域和content域中间是否要填充其他字节数,一般情况下length域后面直接跟上content域,如果配置了该参数,则会在length域和content域之间插入一些字节。
(4)initialBytesToStrip:解码后跳过的字节数。比如解码出来有12个字节,配置了跳过2个字节,则只会往后传10个字节(舍弃掉了头部的两个字节,注意,解码之后往后面传的数据不再携带length域,只用携带content域,所以要跳过length域)
4、二次编解码
在业务层面我更希望拿到的是 某个 entity、或者dto,所以需要二次编码。将bytebuf转成我们业务上需要的对象。第一次编码我们解决的是TCP问题,第二次则是序列化,将byte转成我们业务上要的数据。
第一次解码继承于ByteToMessageDecoder;第二次解码继承于:MessageToMessageDecoder,本质都是集成与ChannelinboundHandlerAdapter。
二次编解码是用户数据(byteBuf)和Java对象之间的转换,netty中也提供了几种开箱即用的编解码工具
例如 StringDecoder、StringEncoder 、ProtobufDecoder、ProtobufEncoder等等。
5、Keepalive+idle监测
keepalive作用:
- 确认TCP连接时通畅的(例如打电话的时候,对方一直在说话,但是其实通讯已经中断了)
- TPC通道是好的,但是对方没有响应(可能服务崩溃了,或者服务处理过来了)
idle监测: 当空闲的时候进行监测(双方如果在发送消息则不需要监测),只负责诊断,诊断后做出不同的行为,决定idle最终用途,一般配合keepavlie来减少keepalive消息。
服务瘦身
在Netty中也有开箱即用的Handler ,IdleStateHandler
TCP中的Keepalive的参数