Netty学习笔记

Netty权威指南学习笔记

Posted by Bug1024 on April 15, 2018

UNIX网络编程IO模型分类

  • 阻塞IO
  • 非阻塞IO
  • IO复用(select/poll/epoll)
  • 信号驱动
  • 异步IO

poll相比select优势

  • FD数目不受限制
  • IO效率不随着FD数目增加而线性下降
  • 使用mmap加速内核与用户空间的消息传递
  • API更为简单

套接字read接口阻塞场景

  • 有数据可读
  • 可用数据已经读取完毕
  • 发送空指针或者IO异常

伪异步IO(线程池方案)的问题

  • 服务端处理缓慢,返回应答耗费60s,平时只需要10ms
  • 线程读取故障节点由于读取输入流堵塞,因而会被阻塞60s
  • 所有可用线程都被阻塞,后续消息只能在队列中排队等待
  • 线程池通常采用阻塞队列实现,队列满之后,后续入队的操作将被阻塞
  • 前端只有一个Acceptor接收客户端请求,新的客户端请求将会被拒绝,产生连接超时
  • 几乎所有连接超时,调用方则认为系统已经崩溃

为何不使用原生NIO编程

  • API复杂,需要熟练掌握Selector,ServerSocketChannel,ByteBuffer等
  • 需要具备其他技能铺垫,例如多线程编程,网络编程
  • 可靠性低,工作量与难度都很大
  • JDK NIO本身也可能有bug

Why Netty

  • API简单
  • 功能强大,预置多种安编码解码,支持多种协议
  • 定制能力强,可通过ChannelHandler对框架灵活扩展
  • 性能高
  • 成熟稳定,社区活跃,经历大规模商业应用考验

TCP粘包和拆包

  • 服务端一次性收到两个数据包,D1和D2粘合在一起,成为粘包
  • 服务端分两次读取两个数据包,第一次读取了完整的D1和D2部分内容,第二次读取到了D2剩余内容,成为拆包
  • 如果TCP滑动窗口笔记小,数据包又比较大,那么容易发生多次拆包

产生的原因:

  • 应用程序写入的字节大小大于套接字发送缓冲区大小
  • 进行MSS大小的TCP分段
  • 以太网帧的payload大于MTU进行IP分片

如何处理:

  • 消息定长,不够的话使用空格填充
  • 包尾使用会车换行分割
  • 消息头+消息体
  • 更复杂协议

通常Netty使用LineBasedFrameDecoder和StringDecoder来解决粘包问题

Netty高性能

  • 异步非阻塞IO类库,基于Reactor模式实现
  • TCP接收和发送缓冲区使用直接内存代替堆内存,避免内存复制
  • 支持通过内存池的方式循环利用ByteBuf,避免频繁创建和销毁ByteBuf带来额外开销
  • 可配置的IO线程数,TCP参数等,满足不同应用场景需要
  • 采用环形数组缓冲区实现无锁化并发编程
  • 合理使用线程安全容器,原子类
  • 关键资源处理使用线程串行方式,避免并发访问带来的锁竞争
  • 通过引用计数器及时申请不再被引用的对象,细粒度的内存管理降低了GC频率

Netty可靠性

  • 链路有效性检测
  • 内存保护机制
  • 优雅停机