《Netty权威指南》笔记——Netty高级特性

  • 第20章 Netty架构剖析
    • Reactor通信调度层
      • 职责链 ChannelPipeline
      • 业务逻辑编排层
    • 关键架构质量属性
      • 高性能
      • 可靠性
      • 可定制性
      • 可拓展性
  • 第21章 Java多线程编程在Netty中的应用
    • Netty的并发编程实践
      • 对共享的可变数据进行正确的同步
      • 正确使用锁
      • volatile的正确使用
      • CAS指令和原子类
      • 线程安全类的应用
      • 读写锁的应用
      • 线程安全性文档说明
      • 不要依赖线程优先级
  • 第22章 高性能之道
    • RPC调用性能模型分析
      • 传统影响调用RPC性能差的三个原因
      • I/O通信性能三原则
    • Netty高性能之道
      • 异步非阻塞通信
      • 高效的Reactor 线程模型
        • **Reactor单**线程: 所有的I/O操作都在同一个NIO线程上面完成,
        • **Reactor多线程**
        • 主从Reactor
      • 无锁化的串行设计
      • 高效的并发编程
      • 高性能的序列化框架
      • 零拷贝
      • 内存池
      • 灵活的TCP参数配置能力
  • 第23章 可靠性
    • Netty高可靠性设计
      • 网络通信类故障
        • 通信对强端强制关闭连接
      • 链路的有效性检测
      • Reactor线程的保护
        • 异常处理要谨慎
        • 规避NIO BUG
      • 内存保护
        • 缓冲区的内存泄露保护
        • 缓冲区溢出保护
      • 流量整形
    • 优化建议
      • 发送队列容量上限控制
      • 回退发送失败的消息

第20章 Netty架构剖析

Reactor通信调度层

该层的主要职责就是监听网络读写和连接操作. 负责将网络层的数据读取到内存缓冲区中, 然后出发各种网络事件, 然后触发各种网络事件, 例如连接创建、连接激活、 读事件、写事件等, 将这些事件触发到PipeLine中, 由PipeLine管理的职责链进行后续处理

由一系列辅助类完成

  • 包括reactor线程NioEventLoop及其父类,
  • NioSocketChannel/NioServerSocketChannel以及其父类
  • ByteBuffer以及由其衍生出来的各种Buffer
  • Unsafe以及其衍生出的各种内部类等.

职责链 ChannelPipeline

负责事件在职责链中的有序传播, 同时负责动态的编排. 可以选择监听和处理自己关心的事件, 它可以拦截处理和向后/向前传播事件. 不同应用的Handler节点的功能也不同,
通常情况下, 往往会开发编解码Handler, 可以将外部的协议消息转换成内部的POJO对象, 这样上层业务则只需要关心处理业务逻辑即可, 不需要感知底层的协议差异和线程模型差异, 实现了架构层面的分层隔离

业务逻辑编排层

  1. 纯粹的业务逻辑编排
  2. 其他的应用层协议插件, 用于特定协议相关的会话和链路管理.

关键架构质量属性

高性能

Netty高性能的具体实现:

  1. 采用异步非阻塞的I/O类库, 基于Reactor模式实现, 解决了传统同步阻塞I/O模式下一个服务端无法平滑地处理线性增长的客户端的问题
  2. TCP接收和发送缓冲区使用直接内存代替堆内存, 避免了内存复制, 提升I/O读取和写入的性能.
  3. 支持通过内存池的方式循环利用ByteBuf, 避免了频繁创建和小会ByteBuf带来的性能损耗
  4. 可配置的I/O线程数, TCP参数等, 为不同的用户场景提供定制化的调优参数, 满足不同的性能场景
  5. 采用环形数组缓冲区实现无锁化并发编程, 代替传统的线程安全容器或者锁
  6. 合理地使用线程安全容器、原子类等, 提升系统的并发处理能力
  7. 关键资源的处理使用单线程串行化的方式, 避免多线程并发访问带来的锁竞争和额外的CPU资源消耗问题
  8. 通过引用计数器及时地申请释放不再被引用的对象, 细粒度的内存管理降低了GC的频率, 减少了频繁GC带来的时延增大和CPU损耗

可靠性

  1. 链路有效性检测

    链路由长连接建立, 不需要每次创建, 可以一直保持

    相应产生的问题, 比如链路空闲问题,也被心跳机制所解决. 为了支持心跳, Netty提供了两种链路空闲检测机制

    1. 读空闲超时机制: 当连续周期T没有消息可读时, 触发超时Handler用户可以基于读空闲超时发送心跳消息, 进行链路检测; 如果连续N个周期仍然没有读取到心跳消息, 可以主动关闭链路
    2. 写空闲超时机制: 当连续周期T没有消息要发送时, 触发超时Handler, 用户可以基于写空闲超时发送心跳信息, 进行链路检测; 如果连续N个周期
  2. 内存保护机制

    Netty提供多种机制对内存进行保护, 包括一下几个方面

    • 通过对象引用计数器对Netty的ByteBuf等内置对象进行细粒度的内存申请和释放, 对非法的对象引用进行检测和保护
    • 通过内存池来重用ByteBuf节省内存
    • 可设置的内存容量上限, 包括ByteBuf、线程池线程数等
  3. 优雅停机

    指的是当系统退出时, JVM通过注册的ShutdownHook拦截到退出的信号量, 然后执行退出操作, 释放相关模块的资源占用, 将缓冲区的消息处理完成或者清空, 将待刷新的数据持久化道磁盘或者数据库中, 等到资源回收和缓冲区消息处理完成之后, 再退出.

可定制性

  • 责任链模式: ChannelPipeline 基于责任链模式开发, 便于业务逻辑的拦截、 定制和扩展
  • 基于接口的开发: 关键的类库都提供了接口或者抽象类, 如果Netty自身的实现无法满足用户的需求, 可以由用户自定义实现相关接口
  • 提供了大量工厂类, 通过重载这些工厂类可以按需创建出用户实现的对象
  • 提供了大量的系统参数供用户按需设置, 增强系统的场景定制性

可拓展性

基于Netty的基础NIO框架, 可以方便地进行应用层协议定制, 例如HTTP协议栈、Thrift, FTP协议栈, 这些都不需要修改Netty源码, 直接基于Netty的二进制类库进行协议的拓展和定制

第21章 Java多线程编程在Netty中的应用

Netty的并发编程实践

对共享的可变数据进行正确的同步

synchronized保证同一时刻, 只有一个线程可以执行某一个方法或者代码块. 同步的作用不仅仅是互斥,它的另一个作用就是共享可变性, 当某个线程修改了可变数据并释放锁后, 其他线程可以获取被修改变量的最新值. 如果没有正确的同步, 这种修改对其他线程是不可见的.

由于 ServerBootStrap 是被外部使用者创建和使用的, 我们无法保证它的方法和成员变量不被并发访问. 因此作为成员变量的options必需进行正确的同步.

正确使用锁

  1. wait 方法用来让线程等待某种条件,它必须在同步块内部被调用, 这个同步块通常会锁定当前对象实例.

    synchronized(this)
    {while(condition)Object.wait;
    }
    
  2. 始终使用 while 循环来调用 wait 方法, 永远不要在循环之外调用wait 方法. 原因是尽管并不满足被唤醒条件, 但是由于其他线程调用 notifyAll() 方法会导致被阻塞线程意外唤醒, 此时执行条件并不满足, 它将破坏被锁保护的约定关系, 导致约束失效, 引起意想不到的结果

  3. 唤醒线程, notify vs notifyAll?
    保守起见是调用notifyAll 唤醒所有等待的线程.

volatile的正确使用

  • 线程可见性: 当一个线程修改了被volatile修饰的变量后, 无论是加锁, 其他线程都可以立即看到最新的修改
  • 禁止指令重排序优化

volatile不能代替传统锁, 答案是不能. volatile仅仅解决了可见性的问题, 但是它并不能保证互斥性, 多个线程并发修某个变量时, 依旧会产生多线程问题.

应用场景来说, volatile最适合使用的是一个线程写, 其他线程读的场合. 如果有多个线程并发操, 仍然需要使用锁或者是线程安全的容器或是源自变量来代替.

CAS指令和原子类

线程安全类的应用

JUC 可以分为4类:

  1. 线程池 Executor Framework 以及定时任务相关的类库, 包括Timer等
  2. 并发集合, 包括List、 Queue、Map 和 Set等
  3. 新的同步器, 如ReadWriteLock
  4. 新的原子包装类, 如 AtomicInteger

建议通过使用线程池, task(Runnable/callable), 原子类和线程完全容器来代替传统的同步锁, wait 和 notify, 以提升并发访问的性能, 降低多线程编程的难度

NioEventLoop是I/O线程, 负责网络读写操作, 同时也执行一些非I/O的任务. 例如时间通知, 定时任务执行等. 需要一个任务队列来缓存这些Task.

NioEventLoop是 ConcurrentLinkedQueue. JDK的线程安全容器底层采用了CAS, volatileReadWriteLock实现, 相比起同步锁, 采用了更轻量, 细粒度的锁, 因此,性能会更高. 合理地应用这些线程安全容器.能提升多线程并发访问的性能, 还能降低开发难度.

Netty对线程池的应用

  1. 定义一个标准的线程池用于执行任务

    private final Executor executor;
    
  2. 赋值并且进行初始化操作

    this.addTaskWakesUp = addTaskWakesUp;
    this.executor = executor;
    taskQueue = newTaskQueue();
    
  3. 执行任务代码

    public void execute(Runnable task){if(task == null){throw new NullPointerException("task");}boolean inEventLoop = inEventLoop();if(inEventLoop){// adding taskto eventloopaddTask(task);}else{// start new thread and adding taskto eventloopstartThread();addTask(task);if (isShutdown() && removeTask(task)){reject();}}
    }
    
  4. startThread(), singleThreadEventExecutor 启动新的线程

    private void startThread(){synchronized (stateLock){if(state == ST_NOT_STARTED){state = ST_STAETED;delayedTaskQueue.add(new ScheduleFutureTask<void>(this, delayedTaskQueue,Executors, <Void> callable(new PurgeTask(), null),
    ScheduledFutureTask.deadlineNanos(SCCHEDULE_PURGE_INTERVAL),SCHEDULE_PURGE_INTERVAL                                                       ));doStartThread();}}
    }
    
  5. 按照I/O任务比例执行任务 Task

    if(selectedKeys != null){processSelectedKeysOptimized(selectedKeys.flip());
    }
    else{processSelectedKeysPlain(selector.selectedKeys());
    }
    final long ioTime = System.nanoTime() - ioStartTime;
    final int ioRatio = this.ioRatio;
    runAllTasks(ioTime * (100-ioRatio)/ioRatio);
  6. 循环从任务队列中获取任务Task并执行

    protected boolean runAllTasks(long timeoutNanos){fetchFromDelayedQueue();Runnable task = pollTask();if(task == null){return false;}final long deadline = ScheduledFutureTask.nanoTime()+timeoutNanos;long runTasks= 0;long lastExecutionTime;for(;;){try{// run taskstask.run();}catch (Throwable t){logger.warn("A task raised an exception",t);}}
    }
    

读写锁的应用

应用场景:

  • 主要用于读多写少的场景, 用来替代传统的同步锁, 以提升并发访问性能
  • 读写锁是可重入, 可降级的, 一个线程获取读写锁后, 可以继续递归获取; 从写锁可以降级为读锁, 以便快速释放锁资源
  • ReentrantReadWriteLock 支持获取锁的公平策略, 在某些特殊的应用场景下, 可以提升并发访问的性能, 同时兼顾线程等待公平性
private void fetchExpiredTimeouts(List<HashedWheelTimeout> expiredTimeouts, long deadline){lock.writeLock().lock();try{fetchExpiredTimeouts(expiredTimeouts, wheel[(int)(tick & mask)].iterator(), deadline);}finally{tick++;lock.writeLock().unlock();}}
)
  • 读写锁支持非阻塞的尝试获取锁 , 如果获取失败, 直接返回false, 而不是同步阻塞. 这个功能在一些场景下非常有用. 例如多个线程同步读写某个资源, 当发生异常或者需要释放资源的时候, 由哪个线程释放是个难题. 因为某些资源不能重复释放或者重复执行, 这样, 可以通过tryLock方法尝试获取锁, 如果拿不到, 说明已经被其他线程占用, 直接退出即可
  • 获取锁之后一定要释放锁, 否则会发生锁溢出异常. 通常的做法是通过finally块释放锁, 如果是 tryLock, 获取锁成功才需要释放锁

线程安全性文档说明

没有线安全性的说明文档, 会让开发人员困扰.

在Netty中, 对于一些关键的类库, 给出了线程安全性的API DOC, 尽管Netty的线程安全性并不是非常完善,

不要依赖线程优先级

线程优先级在不同平台上不一定会正确的执行

第22章 高性能之道

RPC调用性能模型分析

传统影响调用RPC性能差的三个原因

  1. 网络传输方式问题, 因为传统的RPC框架或者基于RMI的方式的远程服务(过程) 调用采用了同步阻塞I/O, 当客户端的并发压力或者网络实验增大之后, 同步阻塞I/O会由于频繁的wait导致I/O线程经常性的阻塞, 由于线程无法高效的工作, I/O处理能力自然下降

    采用BIO通信模型的服务端, 一般由一个独立的Acceptor线程负责监听客户端的连接, 接收到客户端连接之后, 为其创建一个新的线程处理请求信息, 处理完成之后, 返回消息给客户端, 线程销毁, 这就是典型的一请求一应答模型. 该结构最大的问题就是不具备弹性伸缩能力, 当并发访问量增加后, 服务端的线程个数和并发访问数成线性正比, 由于线程是Java虚拟机非常宝贵的系统资源, 当线程数膨胀之后, 系统的性能急剧下降, 随着并发量的继续增加, 可能会发生句柄溢出, 线程堆栈溢出等问题, 并导致服务器最终宕机

  2. 序列化性能差

    典型问题

    1. 是Java内部的一种对象编解码技术, 无法跨语言使用. 例如对于异构系统之间的对接, Java序列化后的码流太大, 无论是网络传输还是持久化到磁盘, 都会导致额外的资源占用.
    2. 相比于其他开源序列化框架, Java序列化后的码流太大, 无论是网络传输还是持久化到磁盘, 都会导致额外的资源占用
    3. 序列化性能差, 资源占用率高 (主要是CPU资源占用高)
  3. 线程模型问题. 由于采用同步阻塞I/O, 会导致每个TCP连接都占用1个线程, 由于线程资源是JVM虚拟机非常宝贵的资源, 当I/O读写阻塞导致线程无法及时释放时, 会导致系统性能急剧下降, 严重的甚至会导致虚拟机无法创建新的线程

I/O通信性能三原则

  1. 传输: 可以选择BIO, NIO 或者AIO, I/O模型很大程度上决定了通信的性能
  2. 协议: HTTP等公有协议或者是内部私有协议. 协议的选择不同, 性能也不同. 相比起共有协议, 内部私有协议的性能通常可以更优化
  3. 线程: 具体在于数据报如何读取? 读取之后的编解码在哪个线程进行, 编解码后的消息如何派发, Reactor线程模型不同, 对性能的影响也非常大

Netty高性能之道

异步非阻塞通信

与Socket和ServerSocket类相对应, NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现. 这两种新增的通道都支持阻塞和非阻塞两种模式.

  1. 阻塞模式: 使用简单, 性能和可靠性都不好
  2. 非阻塞模式: 开发人员一般可以根据自己的需要来选者合适的模式

Netty的I/O线程 NioEventLoop 聚合了 多路复用器: Selector, 可以同时并发处理成百上千个客户端 SocketChannel. 由于读写操作都是非阻塞的, 这就可以充分提升I/O线程的运行效率, 避免由频繁的I/O阻塞导致的线程挂起.

由于Netty采用了异步通信模式, 一个I/O线程可以并发处理N个客户端连接和读写操作, 这从根本上解决了传统同步阻塞I/O一连接一线程模型. 架构的性能、弹性伸缩能力和可靠性都得到了极大的提升.

高效的Reactor 线程模型

  1. Reactor单线程: 所有的I/O操作都在同一个NIO线程上面完成,

    线程职责:

    1. 作为服务端, 接收客户端的TCP连接
    2. 作为客户端, 向服务端发起TCP连接
    3. 读取通信对端的请求或者应答消息
    4. 向通信对端发送消息请求或则应答消息

    由于Reactor模式使用的是异步非阻塞I/O, 所有的I/O操作都不会导致阻塞, 理论上一个线程可以独立处理所有I/O相关的操作. 从架构层面看, 一个NIO线程确实可以完成起承担的职责.

    比如可以拿一个Acceptor接收客户端的TCP连接请求消息, 链路建立成功之后, 通过Dispatch将对应的ByteBuffer派发到指定的Handler上进行消息解码. 用户Handler可以通过NIO线程将消息发送给客户端

    小容量的场景可以用单线程, 但不适应高负载、 大并发的应用因为:

    1. 无法支撑成百上千的链路
    2. 负载过重, 处理速度变慢, 连接超时, 重发, 进而加重, 导致大量消息积压和处理超时 成为系统的性能瓶颈
    3. 影响可靠性, 一旦NIO线程意外运行, 或进入死循环, 导致整个系统通信模块不可用, 节点故障
  2. Reactor多线程

    模式特点:

    1. 专门的NIO线程: Acceptor 用于监听服务端, 接收客户端的TCP连接请求
    2. 网络I/O操作: 读写等由一个NIO线程池负责, 线程池可以采用标准的JDK线程池实现, 它包含一个任务队列和N个可用的线程, 由这些NIO线程负责消息的读取解码编码和发送
    3. 1个NIO线程可以同时处理N条链路, 但是一个链路只对应一个NIO线程, 以防发生并发操作问题

    对于绝大多数场景下, Reactor多线程模型都可以满足性能需求

    特殊应用场景下, 一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题. 例如 百万客户端并发连接, 或者服务端需要对客户端的握手消息进行安全认证, 认证本身非常损耗性能. 所以出现主从

  3. 主从Reactor

    模式特点:

    服务端用于接收客户端连接的不再是一个单独的客户端线程, 而是一个独立的NIO线程池.

    Acceptor 接收到客户端TCP连接请求处理完成后(可能包括介入认证等), 将新创建的SocketChannel注册到I/O线程池(sub reactor线程池) 的某个I/O线程上, 由它负责SocketChannel的读写和辩解码工作. Acceptor线程池只用于客户端的登录、握手和安全认证, 一旦链路建立成功, 就将链路注册到后段subReactor线程池的I/O线程上, 由I/O线程负责后续的I/O操作.

    利用主从NIO线程模型, 可以解决1个服务端舰艇线程无法处理所有客户端连接的性能不足问题. 因此, Netty官方推荐使用该线程模型.

Netty单线程

EventLoopGroup reactorGroup = new NioEventLoopGroup(1);
try{ServerBootstrap b = new ServerBootstrap();b.group(reactorGroup, reactorGroup).channel(NioServerSocketChannel.class)
}

Netty多线程

EventLoopGroup acceptorGroup = new NioEventLoopGroup(1);
EventLoopGroup IOGroup = new NioEventLoopGroup();
try{ServerBootstrap b = new ServerBootstrap();b.group(acceptorGroup, IOGroup).channel(NioServerSocketChannel.class)
}

Netty主从

EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup IOGroup = new NioEventLoopGroup();
try{ServerBootstrap b = new ServerBootstrap();b.group(acceptorGroup, IOGroup).channel(NioServerSocketChannel.class)
}

无锁化的串行设计

并行多线程处理可以提升系统的并发性能. 但假如对共享资源的并发处理不当的话, 会带来锁竞争, 导致性能的下降. 为了尽可能地避免锁竞争带来的性能损耗, 可以通过串行化设计, 就是让消息的处理尽可能在一个线程中完成. 这种局部无锁化的串行线程设计相比一个队列多个工作线程模型性能更优

Netty的NioEventLoop 读取到消息后, 直接调用ChannelPipeline的fireChannelRead(Object msg), 只要用户不主动切换线程, 一直会由NioEventLoop调用到用户的Handler, 期间不尽兴线程切换, 这种串行化处理方式避免了多线程操作导致的锁的竞争, 从性能角度看是最优的

高效的并发编程

Netty的高效并发编程主要体现:

  1. volatile的大量、正确使用
  2. CAS和院子类的广泛使用
  3. 通过读写锁提升并发性能
  4. 线程安全容器的使用

高性能的序列化框架

  1. 序列化后的码流大小(网络带宽的占用)
  2. 序列化 & 反序列化的性能 (CPU资源占用)
  3. 是否支持跨语言 (异构系统的对接和开发语言的切换)

零拷贝

Netty的零拷贝的“零拷贝” 功能主要体现在如下三个方面:

  1. Netty的接收和发送ByteBuffer采用DIRECT BUFFERS, 使用堆外直接内存进行Socket读写, 不需要进行字节缓冲区的二次拷贝. 如果使用传统的堆内存(HEAP BUFFERS) 进行Socket读写, JVM会将对内存Buffer拷贝一份到直接内存中, 然后才写入Socket中, 相比于堆外直接内存, 消息在发送过程中多了一次缓冲区内存拷贝

    为了提升I/O操作的性能, 默认使用direct buffer, 这就避免了读写数据报的二次内存拷贝, 实现了读写Socket的“零拷贝”功能.

  2. "零拷贝"的实现 CompositeByteBuf, 它对外将多个ByteBuf封装成一个ByteBuf, 对外提供统一封装后的ByteBuf接口

    继承关系说明: CompositeByteBuf实际就是个ByteBuf的装饰器, 它将多个ByteBuf组合成一个集合, 然后对外提供统一的ByteBuf接口.

  3. “零拷贝”的第三种就是文件传输, Netty文件传输类 DefaultFileRegion通过transferTo方法将文件发送到目标Channel中, 下面重点看FileChannel的TrasnferTo方法.

很多操作系统直接将文件缓冲去的内容发送到目标Channel中, 而不需要通过循环拷贝的方式, 这是一种更加高效的传输方式, 提升了传输性能, 降低了CPU和内存占用, 实现了文件传输的 “零拷贝”.

内存池

随着JVM虚拟机和JIT即时编译技术的发展, 对象的分配和回收是个非常轻量级的工作. 但是对于缓冲区Buffer, 情况去稍有不同, 特别是对于堆外直接内存的分配和回收, 是一件耗时的操作. 为了尽量重用缓冲区, Netty提供了基于内存池的缓冲区重用机制.

Netty提供了多种内存管理策略. 通过在启动辅助类中配置的相关参数, 可以实现差异化的定制.

测试场景一: 使用内存池分配器创建直接内存缓冲区

int loop= 3000000;
long startTime = Systen.currentTimeMillis();
ByteBuf poolBuffer = null;
for (int i =0; i<loop; i++){poolBuffer = PooledByteBufAllocator.DEFAULT.directBuffer(1024);poolBuffer.WriteBytes(CONTENT);poolBuffer.release();
}

测试场景二: 使用非堆内存分配器创建的直接内存缓冲区

long startTime2 = System.currentTimeMillis();
ByteBuf buffer = null;
for(int i =0; i<loop ; i++){buffer = Unpooled.directBuffer(1024);buffer.writeBytes(CONTENT);
}

采用内存池的ByteBuf相比于朝生夕灭的ByteBuf, 性能高23倍左右 (性能数据与使用场景强相关)

newDirectBuffer方法, 其实是个抽象方法, 由AbstractByteBufAllocator的子类负责实现, 子类实现代码

代码跳转到PooledByteBufAllocator的newDirectBuffer方法, 从Cache中获取内存区PoolArena, 调用它allocate方法进行内存分配

protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity){PoolThreadCache cache = threadCache.get();PoolArena<ByteBUffer> directArena = cache.directArena;ByteBuf buf;if (directArena != null){buf = directArena.allocate(cache, initialCapacity, maxCapacity);}else{if(PlatformDependent.hasUnsafe()){buf = new UnpooledUnsafeDirectByteBuf(this,initialCapacity, maxCapacity);) else{buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);}}return toLeakAwareBuffer(buf);
}

PoolArena 的 allocate方法:

PooledByteBuf<T> allocate(PoolThreadCache cache, int regCapacity, int maxCapacity){/****************************/PooledByteBuf<T> buf = newByteBuf(maxCapacity);/****************************/allocate(cache, buf, reqCapacity);return buf;
}

重点分析 newByteBuf, 是抽象方法, 由子类 DirectArena 和 HeapArena来实现不同类型的缓冲区分配.

DirectArena

static PooledDirectByteBuf newInstance(int maxCapacity){PooledDirectByteBuf buf = RECYCLER.get();buf.setRefCnt(1);buf.maxCapacity(maxCapacity);return buf;
}

通过RECYCLER的get方法循环使用ByteBuf对象, 如果是非内存池实现,则直接创建一个新的ByteBuf对象. 从缓冲池中获取ByteBuf之后, 调用AbstractReferenceCountedByteBuf的setRefCnt方法设置引用计数器,用于对象的引用计数和内存回收(类似JVM垃圾回收机制).

灵活的TCP参数配置能力

合理设置TCP参数在某些场景下对于性能的提升可以起到显著的效果, 例如SO_RCVBUF 和 SO_SNDBUF, 设置不当会有影响.

  1. SO_RCVBUF 和 SO_SNDBUF: 通常建议值为128KB或者256KB

  2. SO_TCPNODELAY: NAGLE算法通过将缓冲区内的小封包自动相连, 组成较大的封包, 阻止大量小封包的发送阻塞网络, 从而提高网络应用效率. 但是对于时延敏感的应用场景需要关闭该优化算法

  3. 软中断: 如果Linux内核支持RPS, RPS开启后可以实现软中断, 提升网络吞吐量. RPS根据数据包的原地址, 目的地址以及目的的和源端口, 计算出一个hash值, 然后根据这个hash值来选者软中断运行的CPU, 从上层来看, 也就是说将每个连接和CPU绑定, 并通过这个hash值, 来均衡软中断在多个CPU上, 提升网络并行处理性能.

第23章 可靠性

Netty高可靠性设计

网络通信类故障

客户端超时

  1. 在同步阻塞I/O模型中, 连接操作时同步阻塞的, 如果不设置超时时间, 客户端I/O线程可能会被长时间阻塞, 这会导致系统可用I/O线程数的减少
  2. 业务层需要 大多数系统都会对业务流程执行时间有限制, 例如WEB交互类的相应时间要小于3S. 客户端设置连接超时时间时为了实现业务层的超时, 往往需要NIO框架或者用户自己封装实现.
通信对强端强制关闭连接

如果发生网络闪断、 双方进程突然宕机或者其他非正舱内关闭链路时间时, TCP链路就会发生异常. 由于TCP是全双工的, 通信双方都需要关闭和释放Socket句柄才不会发生句柄的泄露

对于由于句柄没有被及时关闭导致的功能呢和可靠性问题. 究其原因总结如下:

  1. IO的读写等操作并非仅仅集中在Reactor线程内部, 用户上层的一些定制行为可能会导致IO操作的外逸, 例如业务自定义心跳机制. 这些定制行为加大了统一异常处理的难度, IO操作越发散, 故障发生的概率越大
  2. 一些义仓你分支没有考虑到 , 由于外部环境诱因导致程序进入这些分支, 就会引起故障

链路的有效性检测

心跳检测机制分为三个层面:

  1. TCP层面的心跳检测, 即TCP的keep-alive机制, 它的作用域是整个TCP协议栈
  2. 协议层的心跳检测, 主要存在于长连接协议中. 例如SMPP协议
  3. 应用层的心跳检测, 它主要由各业务产品通过约定范式定时给对方发送心跳消息实现

心跳检测机制也存在差异, 归纳起来主要分为两类

  1. Ping-pong型心跳: 由通信一方定时发送Ping, 对方接收到ping消息之后, 立即返回pong应答消息给对方, 属于请求, 响应型心跳
  2. Ping-ping型: 不区分心跳的请求和应答, 由通信双方按照约定定时向双方发送心跳ping消息, 它属于双向心跳

心跳检测策略:

  1. 连续N次心跳检测都没有收到对方的Pong应答消息或者ping请求消息, 则认为链路已经发生逻辑失效, 这被称作心跳超时
  2. 读取和发送心跳消息的时候如何直接发生了IO异常, 说明链路已经失效, 这被称为心跳失败

Netty提供的空闲检测机制分为三种:

  1. 读空闲, 链路持续时间t没有读取任何消息
  2. 写空闲, 链路持续时间t没有发送任何消息
  3. 读写空闲, 链路持续时间t没有接收或者发送任何消息

Reactor线程的保护

异常处理要谨慎
  1. 某个消息的异常不应该导致整条链路不可用
  2. 某天链路不可用不应该导致其他链路不可用
  3. 某个进程不可用不应该导致其他集群节点不可用
规避NIO BUG

解决策略

  1. 根据该BUG的特征, 首先侦测该BUG是否发生
  2. 将问题Selector上注册的Channel转移到新建的Selector上
  3. 老的问题Selector关闭, 使用新建的Selector替换

内存保护

主要集中在如下几点:

  1. 链路总数的控制: 每条链路都包含接收和发送缓冲区, 链路个数太多容易导致内存溢出
  2. 单个缓冲区的上限控制: 防止非法长度或者消息过大导致内存溢出
  3. 缓冲区内存释放: 防止因为缓冲区使用不当导致的内存泄露
  4. NIO消息发送队列的长度上限控制
缓冲区的内存泄露保护

为了防止因为用户遗漏导致内存泄露, Netty在Pipe line的尾Handler中自动对内存进行释放, TailHandler的内存回收代码

缓冲区溢出保护

缓冲区的创建方式有两种:

  1. 容量预分配,在实际读写过程中如果不够再拓展
  2. 根据协议消息长度创建缓冲区

流量整形

是一种主动调整流量输出速率的措施. 跟流量监管的主要区别在于, 流量整形对流量监管中需要丢弃的报文进行缓存——通常是将它们放入缓冲区或队列内, 也称流量整形(Traffic shaping). 作为高性能的NIO框架, Netty的流量整形有两个作用:

  1. 防止由于上下游网元性能不均衡导致下游网元被压垮, 业务流程中断
  2. 防止由于通信模块接收消息过快, 后段业务线程处理不及时导致的“撑死”问题.

优化建议

发送队列容量上限控制

回退发送失败的消息

《Netty权威指南》笔记 —— 第二十、二十一、二十二, 二十三章相关推荐

  1. netty权威指南笔记-以回车换行结尾的消息如何处理半包问题

    概述 TCP底层会发生粘包和拆包,这个是TCP的一个特性.为了减少网络数据传输的次数,TCP总是希望让网络数据到达一定量级的时候才将数据发送出去,而不是缓存区一有数据就马上发送数据. TCP底层会根据 ...

  2. 《Netty权威指南 第2版》学习笔记(1)---服务端与客户端开发入门

    前言 Netty权威指南中以时间服务器为入门案例,演示了如何通过Netty完成了服务端与客户端之间的交互过程. 在开始使用Netty开发之前,先回顾一下使用NIO进行服务端开发的步骤. 创建Serve ...

  3. netty权威指南 学习笔记http

    序 李林峰的<netty权威指南>,从Java的NIO开始介绍,后面介绍TCP粘包拆包.中级篇介绍编解码技术. 第10章介绍了HTTP及netty HTTP+XML的技术. 因为xml实际 ...

  4. Netty权威指南(四)TCP粘包/拆包问题

    TCP粘包/拆包问题解决之道 上一章 一.介绍 1.1 TCP粘包/拆包问题说明 1.2 TCP粘包/拆包发生的原因 1.3 粘包问题的解决策略 二.未考虑TCP粘包导致的功能异常案例 2.1 Tim ...

  5. 《Hadoop权威指南》第二章 关于MapReduce

    <Hadoop权威指南>第二章 关于MapReduce 目录 使用Hadoop来数据分析 横向扩展 注:<Hadoop权威指南>重点学习摘要笔记 1. 使用Hadoop来数据分 ...

  6. 《Netty权威指南》

    <Netty权威指南> 基本信息 作者: 李林锋 出版社:电子工业出版社 ISBN:9787121233432 上架时间:2014-5-29 出版日期:2014 年6月 开本:16开 页码 ...

  7. HTML5与CSS3权威指南笔记案例1

    第1章 <!DOCTYPE html> <meta charset = "UTF-8"> <title> Search </title&g ...

  8. 《Cassandra权威指南》第二版书评及访谈

    \ 关键点 \ 了解关于Cassandra NoSQL数据库3.0版的功能: \ 如何安装和配置Cassandra数据库,包括集群管理: \ Cassandra数据库的数据模型(概念.逻辑和物理方面) ...

  9. [201504][Netty 权威指南][第2版][李林锋][著]

    [201504][Netty 权威指南][第2版][李林锋][著] https://github.com/wuyinxian124/nettybook2 基础篇 走进 Java NIO 第 1 章 J ...

最新文章

  1. Bitmap上下合成图片
  2. Swift类扩展使用方法
  3. Android 学习视频
  4. Opencv图像保存到电脑及显示
  5. Direct2D (13) : 画刷之 ID2D1BitmapBrush
  6. mysql tuning primer_mysql检测工具tuning-primer.sh
  7. oc总结 --oc基础语法相关知识
  8. HBase Region 自动拆分策略
  9. Android音频框架笔记 - 上篇
  10. 切!原来进入500强就那么简单啊——前IBM,HP,Dell员工揭开外企的招聘内幕
  11. 程序员怒怼产品经理最新表情包,叫我改Bug这辈子是不可能的
  12. ie 无人操作自动关闭_为什么一打开IE浏览器就自动关闭解决办法 IE浏览器打开后马上自动关闭了如何办...
  13. 有哪些图片转excel表格的软件?
  14. 数字认证是做什么的?数字认证有什么用?
  15. 蓝牙突然消失,ubuntu连接音箱没有声音问题
  16. PDF转图片乱码问题解决
  17. 分面导航的详细操作方案
  18. c++ 数组作为参数、返回值
  19. Realtek HD声卡前置面板音频设置教程(前置音频没声音解决办法)
  20. Spring Security OAuth2 实现多人登录互踢下线

热门文章

  1. 仿权重8高收录面包网pc+手机苹果cmsv8影视网站含迅雷下载N430模板
  2. 《左手数据,右手图表》
  3. linux中pwd命令,pwd命令
  4. jQuery UI Datepicker 选择时分秒
  5. Kali Nethunter 如何刷到任何手机上?(终极教程)
  6. 一款勒索病毒的详细分析
  7. C++ std::numeric_limits
  8. 虚拟服务器设置虚拟内存,vmware虚拟机关于内存的一项设置,可以提高你的虚拟机运行效能-虚拟内存怎么设置最好...
  9. MathType如何编辑商标标志
  10. Flask06_ORM多表