Netty特性
多路复用器
Nio 的一个重要组件是 多路复用器 ,底层有3中经典模型,分别是select poll epoll,
一个多路复用器可以处理多个socket连接线程,传统的是一个连接使用一个线程处理。
Netty默认的多路复用是epoll模型,epoll jdk封装的是水平触发的,netty采用的是JNI重写的边缘触发。
Select()方法的主要特点
1.每次都要从用户态到内核态进行拷贝所有的文件描述符
2.要在内核空间遍历所有的文件描述符(如果很大的话,吃不消)
3.内核对文件描述符上限进行了限制 1024(x86)
Epoll
1.包括三个方法 epoll_create 创建epoll句柄
2.Epoll_ctl 只拷贝一次 将文件描述符拷贝到内核中
3.Epoll_wait 等待文件描述符就绪状态 不用全部遍历,减低了操作的时间的复杂度,只用遍历状态发生改变的文件描述符。
Epoll 对 select的改进
1.只拷贝一次到内核空间
2.不用全部遍历,降低了时间复杂度,
3.没有文件描述符个数的限制
4.采用了内存映射机制,内核直接将内核中的就绪队列通过MMAP映射到用户态,避免了额外的内存拷贝带来额外性能的开销。
Netty线程模型
服务端分为两个线程组,一个Boss线程组,一般是一个线程,除非服务端打算监听多个端口,用来处理OP_ACCEPT和客户端的连接,一个work线程组,一般是默认是CPU线程组的2倍。
客户端一般就使用一个work线程组 处理 读写监听就可以了。
流程一般都是:
Boss监听到socketchannel(可能转换为NIOsocketchannel) 之后绑定到work线程组上,监听OP_WRITE/OP_READ事件,当有就绪的读事件发生的时候。
1.开辟内存池空间,用来存放读到的数据
2.编码 - 交个handler处理业务逻辑 - 解码 - 写入到缓存区当中
Netty 编码解码
Tcp传输机制会发生粘包拆包问题。
常见解决tcp粘包 拆包的方法:
1)使用特殊符号表示包结束位置,HTTP / FTP
2)比如 header body在header中存储包的长度
3)传输固定长度的包
TCP使用了一下4中编码解码器,解决TCP粘包 拆包的问题
1)LineBasedFrameDecoder 换行 \n \n\r
2)LengthFieldBasedFrameDecoder 在消息中加入长度值
3)FixedLengthFrameDecoder 固定长度的数据包解码器
4)DelimiterBasedFrameDecoder:类似行拆包器,不过我们可以自定义分隔符。
序列化
Jdk自定义 简单,但是性能太低,序列化之后码流比较大,没办法解决跨语言进行反序列化
Json 数据格式可读性比较好,浏览器支持度非常好
Kryo 性能最好 ,最快 最小 序列化之后的体积最小
Protobuf
Protobuf是google的一个高效协议数据转换的一个工具库,时间效率和空间效率都是json的3-5倍,因为json中会存进去一些不必要的信息,protobuf对这些字段进行了额外的处理,使用tag标识。
缺点:
1.对于具有对象具有泛型属性且有继承的情况,没办法正常反序列化,还原子类信息。
2.可读性比较差,只能通过反序列化进行还原
Kryo是一个快速高效的java对象图形序列化框架,优点是 高效 快速,易用。可变长存储机制,非线程安全的。
零拷贝
Netty的一个特性,主要是发生在操作数据上,无需将数据从一个内存区域拷贝到另一个内存区域上,少一次拷贝,提高cpu的效率。
1.Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
2.Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。
3.Netty底层运用Java NIO的FileChannel.transfer()方法,该方法 依赖操作系统实现零拷贝,可以直接将文件缓冲区的数据发送到目标 Channel中,避免了传统的通过循环写方式导致的内存数据拷贝问题。