如何用Netty写一个高性能的分布式服务框架?( 五 )


 

  • 红黑树O(logN) , 平衡效率和内存占用 , 在容量需求不能确定并可能量很大的情况下红黑树是最佳选择 。
 
  • size参数已经没什么意义 , 早期epoll实现是hash表 , 所以需要size参数 。
 
3)int epoll_ctl(int epfd , int op , int fd , struct epoll_event *event)
 
  • 把epitem放入rb-tree并向内核中断处理程序注册ep_poll_callback , callback触发时把该epitem放进ready-list 。
 
4)int epoll_wait(int epfd , struct epoll_event * events , int maxevents , int timeout)
 
  • ready-list —> events[] 。
 
epoll 的数据结构
如何用Netty写一个高性能的分布式服务框架?

文章插图
 
epoll_wait 工作流程概述
 
对照代码:
linux-2.6.11.12/fs/eventpoll.c:
 
1)epoll_wait 调用 ep_poll
 
  • 当 rdlist(ready-list) 为空(无就绪fd)时挂起当前线程,直到 rdlist 不为空时线程才被唤醒 。
 
2)文件描述符 fd 的 events 状态改变
 
  • buffer由不可读变为可读或由不可写变为可写 , 导致相应fd上的回调函数ep_poll_callback被触发 。
 
3)ep_poll_callback 被触发
 
  • 将相应fd对应epitem加入rdlist , 导致rdlist不空 , 线程被唤醒 , epoll_wait得以继续执行 。
 
4)执行 ep_events_transfer 函数
 
  • 将rdlist中的epitem拷贝到txlist中 , 并将rdlist清空 。
 
  • 如果是epoll LT , 并且fd.events状态没有改变(比如buffer中数据没读完并不会改变状态) , 会再重新将epitem放回rdlist 。
 
5)执行 ep_send_events 函数
 
  • 扫描txlist中的每个epitem , 调用其关联fd对应的poll方法取得较新的events 。
 
  • 将取得的events和相应的fd发送到用户空间 。
 
8 Netty 的最佳实践
 
1)业务线程池必要性
 
  • 业务逻辑尤其是阻塞时间较长的逻辑 , 不要占用netty的IO线程 , dispatch到业务线程池中去 。
 
2)WriteBufferWaterMark
 
  • 注意默认的高低水位线设置(32K~64K) , 根据场景适当调整(可以思考一下如何利用它) 。
 
3)重写 MessageSizeEstimator 来反应真实的高低水位线
 
  • 默认实现不能计算对象size , 由于write时还没路过任何一个outboundHandler就已经开始计算message size , 此时对象还没有被encode成Bytebuf , 所以size计算肯定是不准确的(偏低) 。
 
4)注意EventLoop#ioRatio的设置(默认50)
 
  • 这是EventLoop执行IO任务和非IO任务的一个时间比例上的控制 。
 
5)空闲链路检测用谁调度?
 
  • Netty4.x默认使用IO线程调度 , 使用eventLoop的delayQueue , 一个二叉堆实现的优先级队列 , 复杂度为O(log N) , 每个worker处理自己的链路监测 , 有助于减少上下文切换 , 但是网络IO操作与idle会相互影响 。
 
  • 如果总的连接数小 , 比如几万以内 , 上面的实现并没什么问题 , 连接数大建议用HashedWheelTimer实现一个IdleStateHandler , HashedWheelTimer复杂度为 O(1) , 同时可以让网络IO操作和idle互不影响 , 但有上下文切换开销 。
 
6)使用ctx.writeAndFlush还是channel.writeAndFlush?
 
  • ctx.write直接走到下一个outbound handler , 注意别让它违背你的初衷绕过了空闲链路检测 。
 
  • channel.write从末尾开始倒着向前挨个路过pipeline中的所有outbound handlers 。
 
7)使用Bytebuf.forEachByte() 来代替循环 ByteBuf.readByte()的遍历操作 , 避免rangeCheck()
 
8)使用CompositeByteBuf来避免不必要的内存拷贝


推荐阅读