文章目录
- ByteBuf
- 对堆内存的封装
- 对直接内存封装
- 对复合缓冲区封装
- CompositeByteBuf
- buf相关
- ByteBufHolder
- ByteBufAllocator
- Unpooled
- ByteBufUtil
- ReferenceCounte
- Channel
- EventLoop
- 任务调度
- 线程管理
- EventLoop/线程的分配
- ChannelFuture
- ChannelHandler
- ChannelInboundHandler
- ChannelOutboundHandler
- SimpleChannelInboundHandler
- ChannelHandlerAdapter
- ChannelInitializer
- 编码器
- 解码器
- ChannelPipeline
- ChannelHandlerContext
- Bootstrap
- 资源管理
- 异常处理
- 处理入站异常
- 处理出站异常
- Bootstrap
前言:.因为阅读RocketMQ源码,需要掌握netty,18年肝过一次netty,只是了解了他的架构设计和使用,现在直接肝源码,肝完后去看RocketMQ.然后用netty模仿tomcat写一个web服务器,美滋滋.
.甭废话开肝,
需要掌握技术,NIO,socket编程,多线程以及TCP/IP协议.
使用阻塞IO,面临的问题
1:accept一直阻塞
2:当多用户访问,需要为每个用户新建线程
3:可能存在大量线程休眠状态
4:需要为每个线程开辟内存空间
5:线程过多,上下文切换会频繁.
解决方案:NIO
通过一个线程不断的判断文件句柄数组是否有准备就绪的文件设备,这样就不需要每个线程同步等待,减少了大量线程,降低了线程上下文切换带来的性能损失,提高了线程利用率。这种方式也称为I/O多路复用技术。
select模型需要对数组进行遍历,因此时间复杂度是O(n)因此当高并发量的时候,select模型性能会越来越差。
poll模型和select差不对,但使用的是链表存储,因此不会受限于数组容量限制,解决了并发限制问题,但依旧存在大量连接,遍历查询是否有准备就绪的问题.
解决方案:AIO
通过注册回调事件的方式,当数据可读写时,通过回调方式,将其加入到一个可读写事件的队列中。这样每次用户获取时不需要遍历所有句柄,时间复杂度降低为O(1)。
一个EventLoopGroup 包含一个或者多个EventLoop;
一个EventLoop 在它的生命周期内只和一个Thread 绑定;
所有由EventLoop 处理的I/O 事件都将在它专有的Thread 上被处理;
一个Channel 在它的生命周期内只注册于一个EventLoop;
一个EventLoop 可能会被分配给一个或多个Channel。
ByteBuf
对堆内存的封装
对直接内存封装
对复合缓冲区封装
CompositeByteBuf
buf相关
ByteBufHolder
对buf的池化支持
ByteBufAllocator
PooledByteBufAllocator
池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片
UnpooledByteBufAllocator
不池化ByteBuf实例,并且在每次它被调用时都会返回一个新的实例
Unpooled
ByteBufUtil
提供了用于操作ByteBuf 的静态的辅助方法
ReferenceCounte
引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术
Channel
Socket
ChannelConfig 包含了该Channel 的所有配置设置,并且支持热更新Channel 是独一无二的,所以为了保证顺序将Channel 声明为java.lang.
Comparable 的一个子接口。因此,如果两个不同的Channel 实例都返回了相同的散列码,那
么AbstractChannel 中的compareTo()方法的实现将会抛出一个Error。
ChannelPipeline 持有所有将应用于入站和出站数据以及事件的ChannelHandler 实
例,这些ChannelHandler 实现了应用程序用于处理状态变化以及数据处理的逻辑。
OIO传输
OIO是同步阻塞的,但Netty是异步的.
Netty是如何能够使用和用于异步传输相同的API来支持OIO的呢。
答案就是,Netty利用了SO_TIMEOUT这个Socket标志,它指定了等待一个I/O操作完成的最大毫秒
数。如果操作在指定的时间间隔内没有完成,则将会抛出一个SocketTimeout Exception。Netty
将捕获这个异常并继续处理循环。在EventLoop下一次运行时,它将再次尝试。
Local 传输
用于在同一个JVM 中运行的客户端和服务器程序之间的异步通信。
Embedded 传输
将一组ChannelHandler 作为帮助器类嵌入到其他的ChannelHandler 内部。通过这种方式,你将可以扩展一个ChannelHandler 的功能,而又不需要修改其内部代码。
NIO处理流程
生命周期状态
状态模型
当这些状态发生改变时,将会生成对应的事件。
这些事件将会被转发给ChannelPipeline 中的ChannelHandler,其可以随后对它们做出
响应。
EventLoop
控制流、多线程处理、并发
任务调度
线程管理
EventLoop/线程的分配
ChannelFuture
异步通知
ChannelHandler
一个ChannelHandler 可以从属于多个ChannelPipeline,所以它也可以绑定到多
个ChannelHandlerContext 实例。对于这种用法指在多个ChannelPipeline 中共享同一
个ChannelHandler,对应的ChannelHandler 必须要使用@Sharable 注解标注;否则,
试图将它添加到多个ChannelPipeline 时将会触发异常。显而易见,为了安全地被用于多个
并发的Channel(即连接),这样的ChannelHandler 必须是线程安全的。
处理入站和出站逻辑,提供适配器,主要是实现了一些默认实现
ChannelInboundHandler
处理入站数据以及各种状态变化;
重写channelRead时,需要显式地释放与池化的ByteBuf 实例相关的内存
使用SimpleChannelInboundHandler会自动释放资源
ChannelOutboundHandler
ChannelPromise与ChannelFuture ChannelOutboundHandler中的大部分方法都需要一个ChannelPromise参数,以便在操作完成时得到通知。ChannelPromise是ChannelFuture的一个子类,其定义了一些可写的方法,如setSuccess()和setFailure(),从而使ChannelFuture不可变
SimpleChannelInboundHandler
T:处理的消息的java类型
接收解码消息,处理逻辑
ChannelHandlerAdapter
对ChannelHandler的适配实现.
ChannelInitializer
提供了一种将多个ChannelHandler 添加到一个ChannelPipeline 中的简便方法。initChannel
编码器
处理出站数据并且允许拦截所有的操作。
实现ChannelOutboundHandler
解码器
解码实现ChannelInboundHandler
实现ChannelInboundHandler
ChannelPipeline
处理handler链
ChannelHandler 安装到ChannelPipeline 中的过程如下所示:
一个ChannelInitializer的实现被注册到了ServerBootstrap中①;
当ChannelInitializer.initChannel()方法被调用时,ChannelInitializer
将在ChannelPipeline 中安装一组自定义的ChannelHandler;
ChannelInitializer 将它自己从ChannelPipeline 中移除。
ChannelPipeline 的入站操作
ChannelPipeline 的出站操作
ChannelHandlerContext
管理它所关联的ChannelHandler 和在同一个ChannelPipeline 中的其他ChannelHandler 之间的交互
ChannelHandlerContext 有很多的方法,其中一些方法也存在于Channel 和ChannelPipeline 本身上,但是有一点重要的不同。如果调用Channel 或者ChannelPipeline 上的这些方法,它们将沿着整个ChannelPipeline 进行传播。而调用位于ChannelHandlerContext
上的相同方法,则将从当前所关联的ChannelHandler 开始,并且只会传播给位于该ChannelPipeline 中的下一个能够处理该事件的ChannelHandler。
Bootstrap
为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定的端口,或者将一个进程连接到另一个运行在某个指定主机的指定端口上的进程。
客户端只需要一个EventLoopGroup,服务端需要2个EventLoopGroup(可共用一个)
因为服务端一个用于接收请求,另外一个用来处理已创建的用来处理传入客户端连接的channel
资源管理
ChannelInboundHandler.channelRead和ChannelOutboundHandler.write()方法来处理数据时,都需要确保没有任何的资源泄漏
在使用完某个buf后调整其引用计数.
ResourceLeakDetector对应用程序的缓冲区分配做大约1%的采样来检测内存泄露,利用PhantomReference类来实现,JVM有5种引用,strong soft weak phantom,FinalReference(jvm管理).
java -D io.netty.leakDetectinotallow=ADVANCED
异常处理
处理入站异常
ChannelInboundHandler实现exceptionCaught
因为异常将会继续按照入站方向流动(就像所有的入站事件一样),所以实现了前面所示逻
辑的ChannelInboundHandler 通常位于ChannelPipeline 的最后。这确保了所有的入站
异常都总是会被处理,无论它们可能会发生在ChannelPipeline 中的什么位置。
ChannelHandler.exceptionCaught()的默认实现是简单地将当前异常转发给
ChannelPipeline 中的下一个ChannelHandler;
如果异常到达了ChannelPipeline 的尾端,它将会被记录为未被处理;
要想定义自定义的处理逻辑,你需要重写exceptionCaught()方法。然后你需要决定
是否需要将该异常传播出去。
处理出站异常
每个出站操作都将返回一个ChannelFuture。注册到ChannelFuture 的ChannelFutureListener 将在操作完成时被通知该操作是成功了还是出错了。
几乎所有的ChannelOutboundHandler 上的方法都会传入一个ChannelPromise的实例。作为ChannelFuture 的子类,ChannelPromise 也可以被分配用于异步通知的监听器
Bootstrap
Bootstrap 类负责为客户端和使用无连接协议的应用程序创建Channel
当服务端需要充当客户端,存在线程上下文切换.解决办法,共享同一个EventLoop,因为分配给EventLoop 的所有Channel 都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。如下图