Netty

204 阅读7分钟

Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端

Netty 优势

  1. Netty vs NIO ,NIO 工作量大,bug 多
  2. NIO 需要自己构建协议 2. Netty 解决 TCP 传输问题,如粘包、半包 3. NIO epoll 空轮询导致 CPU100% 4. Netty 对 API 进行增强,使之更易用,如 FastThreadLocal,ByteBuf
  3. 久经考验,Netty 版本
  4. 2.x 2004 2. 3.x 2008 3. 4.x 2013 4. 5.x 废弃(为了兼容 AIO 发现没有明显性能提升,维护成本高)

组件

EventLoop

EventLoop 本质是一个单线程执行器,同时维护了一个 selector,里面 run 方法处理 channel 上源源不断的 IO 事件。

继承关系

  • 继承 j.u.c.ScheduledExecutorService
  • 继承 netty 的 OrderedEventExecutor
    • 提供了 boolean inEventLoop(Thread thread) 判断一个线程是否属于此 EventLoop
    • 提供 parent 方法来看自己属于哪个 EventLoopGroup

EventLoopGroup

是一组 EventLoop,类似于线程数组,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 IO 事件都由此 EventLoop 来处理(保证 IO 事件处理时的线程安全)

  • 继承自 netty 的 EventExecutorGroup
    • 实现了 Iterable 接口提供遍历 EventLoop 能力
    • 另有 next 方法获取集合钟下一个 EventLoop

Channel

channel 的主要作用

  • close() 关闭 channle
  • closeFuture() 处理 channel 的关闭
    • sync() 是同步等待 channel 关闭
    • addListener() 是异步等待 channel 关闭
  • pipeline() 方法添加处理器
  • write() 写入
  • writeAndFlush() 写入数据并刷出
  • sync() 的作用个人理解是同步等待调用方法做完,因为 close、write 等都是由别的线程异步执行的,比如 close().sync() 方法,就是同步等待 close 方法做完之后再往下执行,而 writeAndFlush().sync() 也是 writeAndFlush 执行完再往下,如果是 closeFuture().sync() 就是在 closeFuture 做完后执行

Future & Promise

Future与Promise

Handler & Pipeline

ChannelHandler 用来处理 Channel 上的各种事件,分为入站、出站两种,所有的 Handler 连成一串就是 Pipeline

  • 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端连接,写回结果,执行顺序是先进先出,read 方法是服务端开始入站,write 是服务端开始出站
  • 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工
    • channel.writeAndFlush 出站是后进先出,从 tail 往前
    • ChannelHandlerContext.writeAndFlust 是从当前节点往前遍历 handler

有状态的 ChannelHandler 不是线程安全的,比如 LengthFieldBasedFrameDecoder,一般解码器都不是线程安全的。netty 里类上带 @Sharable 的都是线程安全的。

ByteBuf

ByteBuf 和 ByteBuffer 的区别

  • netty 的 ByteBuf 采用了读写分离的策略(readIndex 和 writeIndex),读写的时候不需要像 ByteBuffer 调用方法来切换读写模式,初始化的 readIndex 和 writeIndex 都为 0,读写索引处于同一位置继续读取会抛出 IndexOutOfBoundsException
  • ByteBuf 可以自动扩容

直接内存和堆内存

  • 直接内存创建和销毁的代价高,但读写性能高,适合配合池化功能一起使用
  • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收管理,但要注意及时主动释放

池化和非池化

池化的意义是可以重用 ByteBuf,优点有:

  • 没有池化,则每次都需要创建新的 ByteBuf,这个操作对直接内存代价昂贵,就算堆内存也会增加 GC 压力
  • 有了池化,则可以重用 ByteBuf 实例,并采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化更节约内存,减少内存溢出的可能性

池化功能是否开启,可以通过下面来设置

-Dio.netty.allocator.type={unpooled|pooled}

4.1 之前,池化功能不成熟,默认是非池化实现
4.1 以后,非 Android 平台默认是启用池化实现,Android 平台启用非池化

扩容

  • 如果写入数据大小未超过 512,则选择下一个 16 的倍数,例如写入后大小为 12,则扩容后的 capacity 是 16
  • 如果写入后数据大小超过 512,则选择下一个 2^n,例如写入后大小为 513,则扩容后 capacity 是 2^10=1024
  • 扩容不能超过 max capacity

retain & relese

由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

  • UnpooledHeapByteBuf 使用的是JVM 内存,只需等 GC 回收内存即可
  • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
  • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

回收内存的源码实现,关注下面方法的不同实现:

protected abstract void deallocate()

Netty 采用了引用计数法来控制内存回收,每个 ByteBuf 都实现了 ReferenceCounted 接口

  • 每个 ByteBuf 对象的初始计数为 1
  • 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
  • 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
  • 当计数为 0 时,底层内存会被回收,这时及时 ByteBuf 对象还在,其各个方法均无法正常使用

谁负责 release?

谁是 ByteBuf 最后使用者,谁负责 release。
虽然 Pipeline 里的 head 和 tail 负责了 release 但是比如我们把 ByteBuf 转成了 String,发往下一个 Handler 时,ByteBuf 也是需要release 的

slice

对原始 ByteBuf 进行切片成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存赋值,还是使用原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的read、write 指针,修改子分片,会修改原 ByteBuf。不能再往子分片里写入新的数据。

duplicate

截取了 ByteBuf 所有内容,没有 max capacity 的限制,与原始的 ByteBuf 使用同一块底层内存,只是读写指针是独立的。

copy

会将底层内存数据进行深拷贝,与原始数据就无关了。

compositeBuffer

把多个 ByteBuf 组合起来,对外提供统一的 readIndex 和 writeIndex。CompositeByteBuf 里有一个 ComponentList,最小容量是 16。不用拷贝数据。

自定义协议

要素

  • 魔数,用来第一时间判断是否是无效数据包
  • 版本号,可以支持协议的升级
  • 序列化算法,消息正文采用哪种序列化反序列化方式,可以由此扩展,例如:json、protobuf、hessian、jdk
  • 指令类型,是登录、注册、单聊、群聊等,跟业务相关
  • 请求序号,为了双工通信,提供异步能力
  • 正文长度
  • 消息正文

参数优化

backlog

Netty-源码学习(1)-BACKLOG参数

TCP三次握手中的全连接与半连接队列

linux 2.2 之前,backlog 大小包括了两个队列的大小,在 2.2 之后,分别使用下面两个参数来看控制

  • sync queue - 半连接队列
    • 通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,在 tcp_syncookies 启用的情况下,逻辑上没有最大值限制,这个设置便被忽略。
  • accept queue - 全连接队列
    • 通过 /proc/sys/net/core/somaxconn 指定,在使用 listen 函数时,比如java中 ServerSocket 的构造函数,内核会根据传入的 backlog 参数与系统参数,取二者的较小值
    • 如果 accept queue 队列满了,server 将会发送一个拒绝连接的错误信息到 client

一、选题的意义及研究状况

(一)选题意义 小学低段数学运算教学是培养学生数学基础的关键阶段,对学生的数学发展和思维能力的培养至关重要。然而,当前存在一些问题,如教学方法不合理、学生兴趣不高等。因此,研究小学低段数学运算教学存在的问题及对策具有重要的现实意义和实践价值。通过深入分析问题并提出对策,可以改进教学质量,提高学生的数学运算能力和学习兴趣。

(二)研究状况 目前已有一些研究关注小学低段数学运算教学存在的问题,并提出了一些改进方法。例如,一些研究关注游戏化教学、情境教学等方法在数学运算教学中的应用,以激发学生的学习兴趣和提高学习效果。然而,这些研究还存在一些局限性,如研究样本的范围有限、对策的具体操作性不强等。因此,有必要进行更加深入和全面的研究,以进一步提升小学低段数学运算教学的质量。