Java框架-netty
netty优势
- api简单,开发快
- 预置很多编码功能,支持多种主流的协议
- 拓展能力强,ChannelHandler可定制化程度高
- 使用nio运行,性能高
- 用的人多
netty特点
- 高性能
采用**
NIO,基于Reactor**模式实现,解决了传统同步阻塞IO模式零拷贝,**
数据缓冲区使用直接内存**代替堆内存,避免了内存复制,提升了IO读取和写入的性能内存池的方式循环利用ByteBuf,避免了频繁插件和销毁ByteBuf带来的性能消耗IO线程数、TCP参数可配置,为**
不同的用户场景**提供定制化的调优参数,满足不同的性能场景采用**
环形数组缓冲区实现无锁化并发**编程,代替传统的线程安全或锁。**
合理**使用线程安全容器,原子类,提升系统的并发处理能力**
关键资源**的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和cpu资源消耗通过**
引用计数法**及时地申请释放不再被引用的对象,细粒度的内存管理降低了GC的频率,减少了频繁GC带来的时延增大和CPU损耗
- 可靠性
- 链路有效监测(心跳和空闲检测)
- 读空闲超时机制
- 写空闲超时机制
- 内存保护机制
- 通过对象引用计数法管理内置对象
- 可设置的内存容量上限,包括ByteBuf、线程池线程数
- 可定制性
- 责任链模式:channelPipeline基于责任链模式开发,便于业务逻辑的拦截、定制和扩展
- 基于接口的开发:关键的类库都提供了接口或者抽象类,用户可以自定义实现相关接口
- 提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象
- 提供大量的系统参数供用户按需设置,增强系统的场景定制
netty工作原理机制
- 组件:
- Channel:通道,底层入口,所有读写最后都是channel进行操作,可以理解为操作系统API的包装类
- Pipeline:管道,数据读写的通道,用于耦合多个handler成责任链,fire方法会取下个handler执行。每个channel有且只有一个Pipeline
- ChannelHandler:通道处理器,自定义业务的接口
- ChannelHandlerContext:通道处理器上下文,每个处理器都有自己的上下文。上下文在Pipeline中成双向链表节点,主要用于执行handler
channel执行时委托给Pipeline,Pipeline负责责任链的构建并从head触发操作,操作处理由双向链表节点Context完成,Context执行handler
- 1、ServerBootstrap配置
- 监听channel配置:bossGroup(监听线程组)、channel(
通道类型,tcp、udp、sctp等)、option(监听channel的可选配置)、attr(监听channel的属性) - 工作channel配置:childGroup(工作线程组)、childOption(工作channel的可选配置)、childAttr(工作channel的属性)、childHandler(
工作处理器) - 调用ServerBootstrap.bind 绑定端口
- 监听channel配置:bossGroup(监听线程组)、channel(
- 2、流程
- ServerBootstrap执行bind时,通过反射创建
监听channel,然后执行初始化(设置配置、属性、添加通道初始化器),通道初始化器用于**添加建立连接的ChannelHandler(ServerBootstrapAcceptor)** - channel执行注册,包括向操作系统注册、bossGroup开始轮训任务、触发fireChannelRegistered向channel**
ServerBootstrapAcceptor** - 轮训到SelectedKey时,触发监听channel的read,在read中创建**
处理channel,然后通过ServerBootstrapAcceptor将处理Channel**注册到childGroup,至此连接建立并交给childGroup处理,开始轮训
- ServerBootstrap执行bind时,通过反射创建
- 3、读写事件传播
read事件由netty触发传播,write事件由我们自己传播- pipeline持有两个哨兵Handler,即HeadContext、TailContext,我们添加的Handler处于这两个Handler之间
- 事件的传播起点、方向、目标:
- 入站事件传播的起点为当前Handler或者HeadContext,方向为
next,也就是往下一个,目标是InboundHandlerctx.fireChannelRead
- 出站事件传播的起点为当前Handler或者TailContext,方向为
prev,也就是往回一个,目标是OutboundHandler- 当前Handler:
ctx.writeAndFlush,TailContext:ctx.channel().writeAndFlush
- 当前Handler:
- 入站事件传播的起点为当前Handler或者HeadContext,方向为
- 4、类图
classDiagram class ServerBootstrap class Eventloop Eventloop: +selector class selector selector: +Map<SelectorKey, Channel> class Channel Channel: +JDKSelectableChannel Channel: +Pipeline class Pipeline Pipeline: +HeadContext->ChannelHandlerContext->...->ChannelHandlerContext->TailContext class ChannelHandler class ChannelHandlerContext class ServerBootstrapAcceptor ChannelHandler <|.. ServerBootstrapAcceptor ServerBootstrap --> EventLoopGroup EventLoopGroup --> Eventloop Eventloop --> selector selector o-- Channel Channel --> Pipeline Pipeline o-- ChannelHandlerContext ChannelHandlerContext --> ChannelHandler
FastThreadLocal
零拷贝
Netty的零拷贝是**JVM层面**的零拷贝。ByteBuf贯穿了零拷贝的设计理念: 尽量避免Buffer复制带来的开销。
比如派生缓冲区(Derived buffers)的操作,duplicate()(复制),slice()(切分),order()(排序)等,虽然都会返回一个新的ByteBuf实例, 但它们****只是具有**自己独立的读索引、写索引和标记索引而已,内部存储(Buffer数据)是共享**的,也就是过程中并没有复制操作。
由此使用这些操作的时候需要**注意**:修改原对象会影响派生对象, 修改派生对象也会影响原对象。
netty零拷贝体现在几个方面:
ByteBuf组合报文数据时,通过 CompositeByteBuf 实现零拷贝,
通过组合多个ByteBuf避免复制通过 wrap 操作实现零拷贝,byte[]数据需要转换成ByteBuf时,对现有数据进行包装;此时修改byte[],ByteBuf也会被修改
通过 slice 操作实现零拷贝。当需要进行数据裁剪时,通过slice可以生成裁剪后的ByteBuf。
新ByteBuf与旧ByteBuf共享数据,但是读写等控制指针独立维护通过 FileRegion 实现零拷贝,FileRegion是基于**
OS的零拷贝实现,上述的是程序上的零拷贝,OS是在IO时的零拷贝**- OS的零拷贝有两种方式:sendfile、mmap内存地址映射,两者都是系统调用
- mmap系统调采用**
内存映射的方式,让内核空间和用户控件共享同一块内存,省去了从内核空间往用户空间复制的开销**。 - sendfile系统调用可以**
将文件直接从硬盘经由DMA传输到套接字缓冲区**,而无需经过用户空间。如果网卡支持收集操作(scatter-gather),那么可以做到真正意义上的零拷贝。 - NIO中FileChannel的map()和transferTo()方法**
封装了底层的mmap和sendfile系统调用**,从而在Java语言上提供了系统层面零拷贝的支持。
- mmap系统调采用**
- OS的零拷贝有两种方式:sendfile、mmap内存地址映射,两者都是系统调用
什么是 TCP 粘包/拆包
- 待发送数据大于发送缓冲去大小或者最大报文长度时会出现拆包
- 相反,待发送数据小于发送缓冲区大小,多次写入缓冲区后才发送数据则出现粘包;接收端没有及时读取数据也会出现粘包
- 处理方法:
- 1、数据包添加包信息,比如包长度,接收端接收时根据长度进行包长度进行处理
- 2、数据包使用固定长度进行发送,接收端每次读取固定长度。不够长度使用0填充
- 3、使用特殊字符对包进行标记分隔
netty的线程模型
- 使用的是Reactor的多路复用线程模型,一般服务端启动的时候会创建两个线程组:BossGroup、WorkerGroup。BossGroup是用来监听并建立客户端连接,workerGroup则是用来读写连接数据和处理逻辑的。
- 每个EventLoopGroup可以包含一个或多个EventLoop,每个EventLoop包含一个nio selector,一个队列,一个线程。其中线程负责轮询selector和处理队列里面的事件
Netty 的零拷贝
- 零拷贝是指netty中使用直接内存进行数据的接收和发送,worker线程进行数据读写时,相比传统io的需要先将数据复制到堆内存,netty并不需要数据读取到jvm内存中,从而减少数据在内存中的复制。
说说操作系统的io模型
- 操作系统io时并不是直接和外部io直接数据交换,而是通过数据缓冲区进行数据交换。缓冲区又分为内核缓冲区和用户缓冲区,当进程需要进行io read时,需要将数据从内核缓冲区拷贝到用户缓冲区;同样io write时,需要将数据从用户缓冲区拷贝到内核缓冲区。
- 不同的io模型,主要围绕进程状态,数据从准备到内核缓冲区的«数据准备»阶段、数据从内核缓冲区到用户缓冲区的«数据复制»阶段两个阶段展开
- 阻塞io:java的socket默认为bio。从数据准备阶段到数据复制阶段都是进程状态都是阻塞,其中数据准备阶段cpu并不需要参与
- 非阻塞io:数据准备阶段,进程不阻塞。进程发起read调用时若数据还没准备好会立即返回进程不会被阻塞。当数据准备好时,则会进入数据拷贝阶段,这个阶段仍然会阻塞线程。由于不知道什么时候数据准备好,所以需要用户进程自己通过轮询检查数据准备情况。
- 多路io复用:非阻塞io中的轮询过程进程是脱不开身的,如果有线程专门处理轮询则可以将进程解放出来。多路io复用select\poll检查io状态,检查到io就绪后随后进行相应的io调用。线程可以同时轮询成百上千的io请求,达到复用的目的。
- 异步io:数据准备阶段、数据拷贝阶段都是不阻塞的。进程发起io请求后直接返回,等待内核数据准备和拷贝,内核处理完后通过发送信号量或者回调通知用户线程。目前只要windows的iocp支持