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

本文详细探讨了Netty的架构设计,包括Reactor通信调度层、ChannelPipeline和业务逻辑编排层,强调了其高性能、可靠性和可定制性。在多线程编程方面,介绍了对共享数据的同步、锁的正确使用、volatile和CAS的应用。此外,还深入讨论了Netty的高性能策略,如异步非阻塞通信、无锁化串行设计和内存池。最后,提到了Netty的可靠性设计,如链路有效性检测和异常处理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第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 eventloop
        addTask(task);
        
      }
      else{
         
        // start new thread and adding taskto eventloop
        startThread();
        addTask(task);
        
        if (isShutdown() && removeTask(task)){
         
          reject();
        }
      }
    }
    
  4. startThread(), singleThreadEventExecutor 启动新的线程

    private void startThread(){
         
      synchronized (stateLock){
         
        if(state == ST_NOT_STARTED)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值