Netty学习笔记

IO模型

IO操作分两步:
1)发起IO请求等待数据准备.
2)实际IO操作.

1. 阻塞与非阻塞
阻塞和非阻塞关注的是: 线程在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

2.同步与异步
同步和异步关注的是:消息通信机制
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是 由调用者主动等待这个调用的结果。
而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状通知来通知调用者,或通过回调函数处理这个调用。

同步须要主动读写数据,在读写数据的过程中还是会阻塞。
异步仅仅须要I/O操作完毕的通知。并不主动读写数据,由操作系统内核完毕数据的读写。
同步IO和异步IO是针对用户应用程序和内核的交互。

Unix提供了五种IO模式,分别是:
阻塞IO
非阻塞IO
IO复用
信号驱动IO
异步IO

前四种IO模型都是同步IO操作,区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用或者select()函数。 相反,异步I/O模型在这两个阶段都要处理。
阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。
同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO。

NioEventLoop

NioEventLoop中维护了一个线程和Selector,线程启动时会调用NioEventLoop的run方法,执行I/O任务(socket中accept、connect、read、write)和非I/O任务(添加到taskQueue中的任务,如register0、bind0等任务)。
两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。

如果触发了epool cpu100%的bug,会发生什么?
selector.select(timeoutMillis)操作会立即返回,不会阻塞timeoutMillis,导致 currentTimeNanos 几乎不变,这种情况下,会反复执行selector.select(timeoutMillis),变量selectCnt 会逐渐变大,当selectCnt 达到阈值,则执行rebuildSelector方法,进行selector重建,解决cpu占用100%的bug。
[selector 如何修复 epoll bug]
1 对 Selector的select操作周期进行统计
2 每完成一次select操作进行一次计数
3 连续发生N(512)次空轮询则认为触发epoll bug,重新rebuild selector

服务端初始化过程

1)创建 boss线程池和work线程池。其中 boss 线程池的线程负责处理请求的 accept 事件,当接收到 accept 事件的请求时,把对应的 socket 封装到一个 NioSocketChannel 中,并交给 work线程池,其中 work 线程池负责请求的 read 和 write 事件,由对应的 Handler 处理
2)设置连接参数(SO_BACKLOG, SO_REUSEADDR)
3) 创建套接字,绑定监听地址和端口(initAndRegister)
a:创建NioServerSocketChannel :创建socket和pipeline
b:给 NioServerSocketChannel 的pipeline添加ChannelHandler 以便其注册到evetloop后调用 (init())
在这里插入图片描述
b:将server channel注册到eventloop (config().group().register(channel); )
c: 调用 b) 注册的handler 。将 ServerBootstrapAcceptor 注册到server channel的pipeline,以便新的连接创建后被调用(将用户指定的childhandler添加到新连接 channel的pipeline,并在 channel注册到eventloop 后被调用)。
d:向 selector 注册需要监听的网络事件(beginRead)。 selectionKey.interestOps(interestOps | SelectionKey.OP_ACCEPT);
e: 监听连接请求。

设计模式

避坑

  1. 在进行消息发送的时候做保护。
    设置消息发送的高低水位,针对消息的平均大小、客户端并发接入数、JVM 内存大小进行计算,得出一个合理的高水位取值。服务端在推送消息时,对 Channel 的状态进行判断(channel.isWritable()),如果达到高水位之后,Channel 的状态会被 Netty 置为不可写,此时服务端不要继续发送消息,防止发送队列积压.

  2. 消息接收防内存泄漏
    Netty 的消息读取并不存在消息队列,但是如果消息解码策略不当,则可能会发生内存泄漏
    2.1 对消息的最大长度做限制,当超过限制之后,抛出解码失败异常,用户可以选择忽略当前已经读取的消息,或者直接关闭链接。
    2.2 Netty 的 ChannelHandler 支持串行和异步并发执行两种策略,==在将 ChannelHandler 加入到 ChannelPipeline 时,如果指定了 EventExecutorGroup,则 ChannelHandler 将由 EventExecutorGroup 中的 EventExecutor 异步执行。==这样的好处是可以实现 Netty I/O 线程与业务 ChannelHandler 逻辑执行的分离,防止 ChannelHandler 中耗时业务逻辑的执行阻塞 I/O 线程。
    如果业务 ChannelHandler 中执行的业务逻辑耗时较长,消息的读取速度又比较快,很容易发生消息在 EventExecutor 中积压的问题,如果创建 EventExecutor 时没有通过 io.netty.eventexecutor.maxPendingTasks 参数指定积压的最大消息个数,则默认取值为 0x7fffffff,长时间的积压将导致内存溢出。

  3. ByteBuf释放
    3.1 在业务 ChannelInboundHandler 继承自 SimpleChannelInboundHandler 或者
    3.2 在业务 ChannelInboundHandler 中调用 ctx.fireChannelRead(msg) 方法,让请求消息继续向后执行,直到调用到
    DefaultChannelPipeline 的内部类 TailContext,由它来负责释放请求消息

4) 基于非内存池的请求 ByteBuf
如果业务使用非内存池模式覆盖 Netty 默认的内存池模式创建请求 ByteBuf
ch.config().setAllocator(UnpooledByteBufAllocator.DEFAULT)
也需要释放

5) 只要调用了 writeAndFlush 或者 flush 方法,在消息发送完成之后都会由 Netty 框架进行内存释放,业务不需要主动释放内存、】
6) 在服务端增加对客户端并发连接数的控制
服务端的流控算法如下:
获取流控阈值。
从全局上下文中获取当前的并发连接数,与流控阈值对比,如果小于流控阈值,则对当前的计数器做原子自增,允许客户端连接接入。
如果等于或者大于流控阈值,则抛出流控异常给客户端。
SSL 连接关闭时,获取上下文中的并发连接数,做原子自减。
在实现服务端流控时,需要注意如下几点:
流控的 ChannelHandler 声明为 @ChannelHandler.Sharable,这样全局创建一个流控实例,就可以在所有的 SSL 连接中共享。
通过 userEventTriggered 方法拦截 SslHandshakeCompletionEvent 和 SslCloseCompletionEvent 事件,在 SSL 握手成功和 SSL 连接关闭时更新流控计数器。
流控并不是单针对 ESTABLISHED 状态的 HTTP 连接,而是针对所有状态的连接,因为客户端关闭连接,并不意味着服务端也同时关闭了连接,只有 SslCloseCompletionEvent 事件触发时,服务端才真正的关闭了 NioSocketChannel,GC 才会回收连接关联的内存。
流控 ChannelHandler 会被多个 NioEventLoop 线程调用,因此对于相关的计数器更新等操作,要保证并发安全性,避免使用全局锁,可以通过原子类等提升性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值