Netty总结

Netty,异步事件驱动网络框架。
高效原因:
1、使用的是IO多路复用。
2、零拷贝,堆外直接内存。
3、强大网络API。

netty核心组成jdk的nio三剑客:Channel、Buffer、Selector.

服务器端Netty具体线程模型
1、定义引导类ServerBootstrap
2、ServerBootstrap 绑定两个事件循环组NioEventLoopGroup。
NioEventLoopGroup由一个或者多个NioEventLoop组成,一个循环组用于接收链接请求(bossGroup),一个循环组进行事件处理(workerGroup)。
3、每个NioEventLoop会绑定一个Selector,Selector进行监听就绪事件进行循环处理,每个请求会创建一个Channel,Channel注册到Selector上。
4、从3可以得出结论,每个Channel会绑定一个NioEventLoop,可以理解为一个请求绑定一个线程,因此每个连接都会按照顺序执行所有ChannelHandler,都会在同一个线程上执行。不会存在线程先后顺序问题。更重要的是ChannelPipline链表必须严格按照顺序执行的。单线程的设计能够保证ChannelHandler的顺序执行。

Netty工作原理
1、server端工作原理

server端启动时绑定本地某个端口,将自己NioServerSocketChannel注册到某个boss NioEventLoop的selector上。
server端包含1个boss NioEventLoopGroup和1个worker NioEventLoopGroup,NioEventLoopGroup相当于1个事件循环组,这个组里包含多个事件循环NioEventLoop,每个NioEventLoop包含1个selector和1个事件循环线程。
每个boss NioEventLoop循环执行的任务包含3步:

  • 轮询accept事件;
  • 处理io任务,即accept事件,与client建立连接,生成NioSocketChannel,并将NioSocketChannel注册到某个worker NioEventLoop的selector上;
  • 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务。

每个worker NioEventLoop循环执行的任务包含3步:

  • 轮询read、write事件;
  • 处理io任务,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理;
  • 处理任务队列中的任务,runAllTasks。

2、client端工作原理

client端启动时connect到server,建立NioSocketChannel,并注册到某个NioEventLoop的selector上。

client端只包含1个NioEventLoopGroup,每个NioEventLoop循环执行的任务包含3步:

  • 轮询connect、read、write事件;
  • 处理io任务,即connect、read、write事件,在NioSocketChannel连接建立、可读、可写事件发生时进行处理;
  • 处理非io任务,runAllTasks。

ByteBuf
ByteBuf是一个节点容器,里面数据包括三部分:

  • 已经丢弃的数据,这部分数据是无效的
  • 可读字节,这部分数据是ByteBuf的主体
  • 可写字节
    这三段数据被两个指针给划分出来,读指针、写指针。
    ByteBuf 本质上就是,它引用了一段内存,这段内存可以是堆内也可以是堆外的,然后用引用计数来控制这段内存是否需要被释放,使用读写指针来控制对 ByteBuf 的读写,可以理解为是外观模式的一种使用

基于读写指针和容量、最大可扩容容量,衍生出一系列的读写方法
多个 ByteBuf 可以引用同一段内存,通过引用计数来控制内存的释放,遵循谁 retain() 谁 release() 的原则
ByteBuf和ByteBuffer的区别

  • 可扩展到用户定义的buffer类型中
  • 通过内置的复合buffer类型实现透明的零拷贝(zero-copy)
  • 容量可以根据需要扩展
  • 切换读写模式不需要调用ByteBuffer.flip()方法
  • 读写采用不同的索引
  • 支持方法链接调用
  • 支持引用计数

NioEventLoop
NioEventLoop除了要处理IO事件,还有主要:

非IO操作的系统Task

  • 定时任务
  • 非IO操作和IO操作各占默认值50%,底层使用Selector(多路复用器)

Selector BUG出现的原因
若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%,

Netty的解决办法

  • 对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。
  • 重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。

Netty 自带的拆包器
1、固定长度的拆包器 FixedLengthFrameDecoder
如果你的应用层协议非常简单,每个数据包的长度都是固定的,比如 100,那么只需要把这个拆包器加到 pipeline 中,Netty 会把一个个长度为 100 的数据包 (ByteBuf) 传递到下一个 channelHandler。
2、行拆包器 LineBasedFrameDecoder
从字面意思来看,发送端发送数据包的时候,每个数据包之间以换行符作为分隔,接收端通过 LineBasedFrameDecoder 将粘过的 ByteBuf 拆分成一个个完整的应用层数据包。
3、分隔符拆包器 DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不过我们可以自定义分隔符。
4、基于长度域拆包器 LengthFieldBasedFrameDecoder
这种拆包器是最通用的一种拆包器,只要你的自定义协议中包含长度域字段,均可以使用这个拆包器来实现应用层拆包。

Netty如何保证异步串行无锁?
NioEventLoop封装了一个线程,这个线程叫IO线程,专门处理客户端链接, 读写事件, 还有一个重要的事情也是由这个IO线程去做的, 是啥呢? 就是处理队列中的任务. 没错, 每个NioEventLoop都有一个队列, 这个队列是在创建NioEventLoop时被初始化。

源码继承关系:

SingleThreadEventExecutor中定义的IO线程

    private volatile Thread thread;
    @Override
    public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
    }

同样此类定义了任务队列:


Mpsc意思是多个生产者单个消费者Mp(producer)s(single)c(consumer)

其他的线程, 也就是多个生产者, 如果需要进行读写操作, 就把读写操作封装成任务放在这个队列中, 然后由这个NioEventLoop封装的IO线程去消费. 当然这个队列的设计是线程安全的。
线程执行队列任务先判断是否是封装的线程,如果是正常执行。

Netty是在哪里检测有新连接接入的?
答:Boss线程通过服务端Channel绑定的Selector轮询OP_ACCEPT事件,通过JDK底层Channel的accept()方法获取JDK底层SocketChannel创建新连接

新连接是怎样注册到NioEventLoop线程的?
答:Worker线程调用Chooser的next()方法选择获取NioEventLoop绑定到客户端Channel,使用doRegister()方法将新连接注册到NioEventLoop的Selector上面

posted @ 2022-06-30 15:44  倔强的老铁  阅读(128)  评论(0编辑  收藏  举报