You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

765 lines
34 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Netty
## 目录
- Netty 的背景现状趋势
- Netty 源码 从 领域知识分析
- Netty 源码 从 请求处理分析
- Netty 基本原理编码
- Netty 生产级实战
- Netty 精通
---
## 1. 概述
- 官网 https://netty.io/
- 用户指导文档 User guide for 4.x https://netty.io/wiki/user-guide-for-4.x.html
- API文档 4.1.79.Final https://netty.io/4.1/api/index.html </br>
![Netty官方介绍图](pic/Netty官方介绍图.png)
- Netty 是 Trustin Lee (韩国, Line 公司) 2004 研发
- 本质: 网络应用程序框架 - 非单机版程序
- 实现: 异步, 事件驱动 - 高性能
- 特性: 高性能, 可维护, 快速开发 - 能快速开发
- 用途: 开发服务器和客户端
---
- 思考: 为什么不直接使用 JDK NIO
- Netty做的优化:
- 支持常用应用层协议
- 解决传输问题: 粘包, 半包现象
- 支持流量整形
- 完善的断连, ldle等异常处理
- 规避 JDK NIO bug - 经典的 epoll bug : 异常唤醒空转导致 CPU 100%
- Netty 解决方式: 检测问题发生, 然后处理
- io.netty.channel.socket.nio.NioChannelOption#setOption
- IP_TOS 参数(IP包的优先级和QoS选项)使用时抛出异常 - java.lang.AssertionError:Option not found
- Netty 解决方式: 遇到问题绕路走
- Netty API 更友好和强大
- 比如 ByteBuffer 和 Netty 的 ByteBuf
- Threadlocal 和 Netty 的 FastThreadLocal
- Netty 隔离变化 屏蔽细节
- 隔离 JDK NIO 的实现变化: nio -> nio2(aio) -> ...
- 屏蔽 JDK NIO 的实现细节
- 思考: 自己去开发一个 Netty 的类似框架需要什么
- 需要你自己去维护基础 Nio 的 bug
- 且连续的维护很久
---
- 类似的网络通信框架
- **Apache Mina** - Netty 是 Mina 的升级版本
- **Sun Grizzly** - 用得少,文档少,更新慢
- **Apple Swift NIO ACE** 等 - 语言不统配
- **Cindy** 等 - 生命周期短
- **Tomcat, Jetty**等 - 没有独立出来
- 版本发展
- 2004年6月 Netty2 发布
- 声称 Java 社区中第一个基于事件驱动的应用网络框架
- 2008年10月 Netty3 发布
- 2013年7月 Netty4 发布
- 2013年12月 发布 5.0.0.Alpha1
- 2015年11月11日 废弃 5.0.0
- 废弃原因1: 复杂,没有证明明显性能优势, 维护不过来
- 废弃原因2: 与 Apache Mina 关系
---
- 现状与趋势
- 维护者 core: Trustin Lee & Norman Maurer
- 分支
- 4.1 master 支持 Android
- 4.0 线程模型优化, 包结构, 命名
- Netty 无处不在
- 典型项目使用
- 数据库: Cassandra
- 大数据处理: Spark Hadoop
- Message Queue: RocketMQ
- 检索: ElasticSearch
- 框架: gRPC, Apache Dubbo, Spring5Spring WebFlux
- 分布式协调器: Zookeeper
- 工具类: async-http-client
- 等...
- 趋势
- 更多流行协议的支持
- 紧跟 JDK 新功能的步伐
- 更多易用, 人性化的功能
- IP地址黑名单, 流量整形
- 应用越来越多
---
## 2. 原理剖析
### 2.1 I/O 模式
- 经典的 I/O 模式
- BIO
- NIO
- AIO
- 概念
- 阻塞和非阻塞
- 同步和异步
- 为什么 Netty 仅支持 NIO
- 为什么不建议 阻塞 I/O (BIO/OIO)
- 连接数高的情况下: 阻塞 -> 耗资源,效率低
- 为什么删掉已经做好的AIO支持?
- Windows 实现成熟,但是恩少用来做服务器
- Linux 常用来做服务器, 但是AIO实现不够成熟
- Linux 下 AIO 相比较 NIO 的性能提升不明显
- ![Netty的多种NIO实现](pic/Netty的多种NIO实现.png)
- 为什么 Netty 有多种 NIO 实现
- 通用的 NIO 实现在 Linux 下也是使用 epoll, 为什么自己单独实现?
- 实现的更好
- Netty 暴露了更多的可控制参数
- JDK 的 NIO 默认实现是水平触发
- Netty 是边缘触发(默认)和水平触发可切换
- Netty 实现的垃圾回收更好, 性能更好
- NIO 一定优于 BIO 么
- BIO 代码简单
- 特定场景: 连接数少, 并发度低, BIO 性能不输 NIO
- Netty 切换 I/O 模式支持
- 在 new EventLoopGroup 的实现的时候和在指定 channel 的使用同时指定对应的 I/O 模式支持即可
- EventLoopGroup 采用的是 死循环监听 + 处理事件
- Netty channel 采用的是 泛型 + 反射 + 工厂模式 实现的 I/O 模式的切换
### 2.2 Netty 支持三种 Reactor
- Reactor 是一种开发模式,模式的核心流程
- 注册感兴趣的事件 -> 扫描是否有感兴趣的事件发生 -> 事件发生后做出相应的处理
- ![Netty中使用Reactor模式](pic/Netty中使用Reactor模式.png)
- 主从 Reactor 模式是最常用的
### 2.3 Netty 支持 TCP粘包,半包
- 粘包的主要原因
- 发送方每次写入数据 < 嵌套字缓冲区大小
- 接收方读取套接字缓冲区数据不够及时
- 半包的主要原因
- 发送方写入数据 > 套接字缓冲区大小
- 发送的数据大于协议的MTU(Maximum Transmission Unit, 最大传输单元), 必须拆包
- 换个角度来看
- 收发
- 一个发送可能被多次接收, 多个发送可能被一次接收
- 传输
- 一个发送可能占用多个传输包, 多个发送可能公用一个传输包
- 根本原因
- TCP 是流式协议, 消息无边界
- PS: UDP虽然一次运输多个,但是每个传输单元都有界限, 一个一个接收,所以无粘包, 半包问题
- ![找出消息边界的方式](pic/找出消息边界的方式.png)
- ![Netty对三种常用封帧方式的支持](pic/Netty对三种常用封帧方式的支持.png)
### 2.4 Netty 对 "二次" 编解码方式的支持
- TCP 包法人编解码是一次编解码, 但是我们要对一次解码的字节进行更好的使用, 所以要对所使用的对象进行转换
- 对应的编解码器就是为了将Java对象转换成字节流方便存储或传输
- 一次解码器: ByteToMessageDecoder
- io.netty.buffer.ByteBuf (原始数据流) -> io.netty.buffer.ByteBuf (用户数据)
- 二次解码器: MessageToMessageDecoder<I>
- io.netty.buffer.ByteBuf (用户数据) -> Java Obj
- 是否要把两次编解码合二为一?
- 不建议
- 没有分层, 不够清晰
- 耦合性高, 不容易置换方案
- 常用的"二次"编解码方式
- Java 序列化
- Marshaling
- XML
- JSON
- MessagePack
- Protobuf
- 其他
- 选择依据 - 需要比较不同的数据大小情况
- 空间: 编码后占用空间
- 时间: 编解码速度
- 是否追求可读性
- 多语言的支持
- Google Protobuf
- Protobuf 是一个灵活的, 高效的用于序列化数据的协议
- 相对比XML和JSON, Protobuf 更小, 更快, 更便捷
- Protobuf 是跨语言的, 并且自带了一个编译器(protoc), 只需要用它进行编译,可以自动生成 Java,Python 等代码, 不需要自己进行编写
- 使用
```shell
# 定义 .proto 文件
# 安装工具
# 执行生成文件
protoc --java_out=[生成文件的目录]
```
- ![Proto的使用](pic/Proto的使用.png)
### 2.5 keepalive 与 idle 监测
- 为什么还需要应用层的 keepalive
- 协议不同, 各层的关注点不同:
- 传输层关注是否 "通", 应用层关注是否可服务?
- TCP 层的 keepalive 默认关闭, 且经过路由中转设备 keepalive 包可能会被丢弃
- TCP层的 keepalive 时间太长
- 默认 > 2 小时 , 但属于系统参数, 改动影响所有应用
- Tips: HTTP 属于应用层协议, 但是常常听到的名词 "HTTP Keep-Alive" 指的是对长连接和短连接的选择
- Connection: Keep-Alive 长连接 (HTTP/1.1 默认长连接, 不需要带这个 header)
- Connection: Close 短连接
- idle 监测是什么?
- 发送 keepalive: 一般用来配合 keepalive, 减少 keepalive 消息
- KeepAlive 设计演进 : V1 定时 keepalive 消息 -> V2 空闲监测 + 判定为 idle 时才发 keepalive
- 实际应用: 结合起来使用, 按需 keepalive, 保证不会空闲, 如果空闲, 关闭连接
- 在 Netty 中开启 TCP keepalive 和 idle 检测
- 开启 keepalive (Server 端开启 TCP keepalive)
- bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true)
- bootstrap.childOption(NioChannelOption.of(StandardSocketOptions.SO_KEEPALIVE), true)
- 提示: .option(ChannelOption.SO_KEEPALIVE, true) 存在但是无效
- 开启不同的 idle Check
- ch.pipeline().addLast("idleCheckHandler", new IdleStateHandler(0,20,TimeUnit.SECONDS));
### 2.6 Netty 锁 相关的支持
- 同步问题的核心三要素
- 原子性
- 可见性
- 有序性
- 锁的分类
- 对竞争的态度: 乐观锁(java.util.concurrent 包中的原子类), 悲观锁(Synchronized)
- 等待锁的人是否公平而言: 公平锁 new ReentrantLock(true) 与 非公平锁 new ReentrantLock()
- 是否可以共享: 共享锁与独享锁: ReadWriteLock, 其读锁是共享锁, 其写锁是独享锁
- 在意锁的对象和范围 -> 减少粒度
- 例: 初始化 channel (io.neety.bootstrap.ServerBootstrap#init) # 该代码已经被 Netty 重构
- Synchronized method -> Synchronized block
- 在意锁的对象本身大小 -> 减少空间占用
- 例: 统计待发送的字节数(io.neety.channel.ChannelOutboundBuffer)
- AtomicLong -> Volatile long + AtomicLongFieldUpdate
- Atomic long VS long:
- volatile long = 8 bytes
- AtomicLong = 8 bytes(volatile long) + 16bytes(对象头) + 8 bytes(引用) = 32 bytes [不考虑优化]
- 至少节省了 24 个 字节
- 结论: Atomic* objects -> Volatile primary type + Static Atomic* FieldUpdater
- 注意锁的速度 -> 提高并发性
- 记录内存分配字节数等功能用到的 LongCounter
- (io.netty.util.internal.PlatformDependent#newLongCounter)
- 高并发时候: AtomicLong -> LOngAdder
- 结论: 及时衡量, 使用JDK最新的功能
- ![Netty锁的并发性提升](pic/Netty锁的并发性提升.png)
- 根据不同的场景选择不同的并发包 -> 因需而变
- 关闭和等待关闭事件执行器(Event Executor):
- Object.await/notify -> CountDownLatch
- ![Netty并发包因需而变](pic/Netty并发包因需而变.png)
- ![Netty并发包的因需而变2](pic/Netty并发包的因需而变2.png)
- 衡量好锁的价值, 能不用就不用
- 锁管理一定要和生活中的实际场景息息相关, 服务员服务包厢的想法
- 局部串行: Channel的 I/O 请求处理是串行的
- 整体并行: 多个串行化的线程(NioEventLoop)
- ![Netty衡量锁的价值](pic/Netty衡量锁的价值.png)
- Netty 的应用场景: 局部串行 + 整体并行 -> 一个队列 + 多个线程模式:
- 降低用户开发难度, 逻辑简单, 提升处理性能
- 避免锁带来的上下文切换和并发保护等额外开销
- 避免用锁: 用 ThreadLocal 来避免资源争用,例如 Netty 轻量级的线程池实现
- io.netty.util.Recycler#threadLocal
### 2.7 Netty 内存的管理和使用
- 目标:
- 内存占用少(空间)
- 应用速度快(时间)
- 对 Java 而言, 减少 Full GC 的 STW(Stop the world)时间
- Netty 内存技巧 - 减少对象本身大小
- 能用基本类型就不要使用包装类型
- 应该定义成类变量的不要定义为实例变量:
- 一个类 -> 一个变量
- 一个实例 -> 一个实例变量
- 一个类 -> 多个实例
- 实例越多, 浪费就越多
- Netty对前两者的实际使用
- ![Netty内存技巧减少内存本身大小](pic/Netty内存技巧减少内存本身大小.png)
- Netty 内存技巧 - 对分配内存进行预估
- ![Netty内存技巧对分配内存预估](pic/Netty内存技巧对分配内存预估.png)
- Netty 内存技巧 - 预测分配大小
- ![Netty内存技巧预测分配大小](pic/Netty内存技巧预测分配大小.png)
- Netty 内存技巧 - Zero-Copy
- 使用逻辑组合
- ![Netty内存技巧ZeroCopy](pic/Netty内存技巧ZeroCopy.png)
- 使用包装
- ![Netty内存技巧ZeroCopy1](pic/Netty内存技巧ZeroCopy1.png)
- 使用 JDK 的 Zero-Copy 接口
- ![Netty内存技巧ZeroCopy2](pic/Netty内存技巧ZeroCopy2.png)
- Netty 内存技巧 - 堆外内存
- 生活中的场景 - 门口烧烤摊坐不下, 在门口放点桌子
- 店内 -> JVM 内存 -> 堆(heap) + 非堆 (non heap)
- 店外 -> JVM 外部 -> 堆外(off heap)
- Netty 使用堆外内存
- ![Netty使用堆外内存](pic/Netty使用堆外内存.png)
- Netty 内存技巧 - 内存池
- 类似: 平板点菜 -> 替代纸点菜
- 为什么要引入对象池:
- 创建开销大
- 对象高频率创建且可复用
- 支持高并发又能保护系统
- 维护, 共享有限的资源
- 如何实现内存池?
- 开源实现: Apache Common Pool
- Netty 轻量级对象池实现 io.netty.util.Recycler [他自己的场景来建立的]
## 4. 基本原理编码
### 4.1 编写网络应用程序的基本步骤
- ![编写网络应用程序的基本步骤](pic/编写网络应用程序的基本步骤.png)
- ![编写网络应用程序的基本步骤1](pic/编写网络应用程序的基本步骤1.png)
### 4.2 数据结构设计
- ![Netty案例数据结构设计](pic/Netty案例数据结构设计.png)
- opration/ opration result - 封装成 Body [这里采用的是Json编码]
- version - 头信息, 版本号 - 处理兼容性
- opCode - 头信息, opration 类型 - Json解析的时候用到
- streamId - 头信息, 标识信息唯一的Id
- length - 处理粘包和半包问题
### 4.3 代码
- case 查看 com.baiye.case5
### 4.4 Netty 编程中的易错点
- LengthFieldBasedFrameDecoder 中 initialBytesToStrip 未考虑设置
- 见 com.baiye.case5.server.codec.OrderFrameDecoder
- 见 io.netty.handler.codec.LengthFieldBasedFrameDecoder 源码
- >* lengthFieldOffset = 0
>* lengthFieldLength = 2
>* <b>lengthAdjustment</b> = <b>-2</b> (= the length of the Length field)
>* initialBytesToStrip = 0
>*
>* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
>* +--------+----------------+ +--------+----------------+
>* | Length | Actual Content |----->| Length | Actual Content |
>* | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
>* +--------+----------------+ +--------+----------------+
>
- 一定要进行设置,这样才会跳过 Length field 的长度
- 避免使用 LengthFieldBasedFrameDecoder(...) 最简单的构造器, 因为最简单的构造器 initialBytesToStrip 是 0
- ChannelHandler 顺序不正确
- 如何保证顺序正确:
- 先请求后响应, 包 -> 协议 -> 协议 -> 包
- 业务的Handler 顺序都可以 , 可以放在最后写
- ChannelHandler 该共享不共享, 不该共享却共享,触发多线程问题
- LoggingHandler 就是要共享的, 因为Channel 都有自己的 pipeline , 不共享就是浪费空间, 问题也不大
- PS: 总结有哪些需要共享的 Handler, 哪些不需要共享的 Handler
- 分配 ByteBuf: 分配器直接用 ByteBufAllocator.DEFAULT 等, 而不是采用 ChannelHandlerContext.alloc()
- 见 com.baiye.case5.server.codec.OrderProtocolEncoder
- ctx 中可以获取 alloc, 不要自己去创建, 因为当源码重新创建了一个 alloc 的实现,会有可能造成实现的不一致
- 未考虑 ByteBuf 的释放
- 继承 SimpleChannelInboundHandler 可以帮我们进行释放资源, 如果使用 ChannelInboundHandlerAdapter 就要自己进行释放 ReferenceCountUtil.release(msg); , 不接收消息也要进行释放 fireChannelRead()
- 错以为 ChannelHandlerContext.writer(msg) 就写出数据了
- writer 仅仅是将消息加到队列中, 不是进行真正的发送
- 乱用 ChannelHandlerContext.channel().writerAndFlush(msg)
- ctx.writeAndFlush(responseMessage); : 是在当前 pipelineHandler 的位置寻找下一个符合条件的 Handler,所以并不是把 pipeline 重新走了一遍
- ctx.channel().writerAndFlush(msg); : 是调用的 pipeline.writeAndFlush(msg); , 表示 pipeline 重新走了一遍, 如果是中间的 Handler 就会造成死循环
- ctx.channel().writerAndFlush(msg); 常用在客户端, ctx.writeAndFlush(responseMessage); 用在服务端
## 5. 实战及调优
### 5.1 调优参数: 调整 System 参数及 Netty 核心参数
- Linux 系统参数
- ![调整System参数](pic/调整System参数.png)
- 1: 查看 云服务器 支持不支持这种帮助你进行一键调优的工具或者配置
```shell
/proc/sys/net/ipv4/tcp_keepalive_time
```
---
- Netty 支持的系统参数
- Netty 支持的系统参数 ChannelOption.XXX 讨论
- 不考虑 UDP
- IP_MULTICAST_TTL
- 不考虑 OIO 编程
- ChannelOption<Integer>SO_TIMEOUT=("SO_TIMEOUT"); - 控制阻塞时间
- 参数列表
- ![Netty支持的调优参数表](pic/Netty支持的调优参数表.png)
- SO_SNDBUF 和 SO_RCVBUF 参数现在因为Linux是动态的, 所以不用调整
- SO_KEEPALIVE 我们用应用层控制, 所以这里保持默认关闭
- SO_REUSEADDR
- SO_LINGER
- IP_TOS
- TCP_NODELAY 这个要设置为 true - 小报文比较多的情况下
- ![Netty支持的调优参数表1](pic/Netty支持的调优参数表1.png)
- SO_RCVBUF
- SO_REUSEADDR
- SO_BACKLOG
- ~~IP_TOS~~
- 参数调优要点 - 权衡 Netty 核心参数
- option/childOption 要分清, 不会报错, 也不会生效
- 不懂不要动, 避免过早优化
- 可配置(动态配置更好)
- 需要调整的参数
- 最大打开文件数
- TCP_NODELAY SO_BACKLOG SO_REUSEADDR (酌情处理)
- ![Netty支持的调优参数表2](pic/Netty支持的调优参数表2.png)
- ![Netty支持的调优参数表3](pic/Netty支持的调优参数表3.png)
- 第一个参数值, 不大是因为这是每个连接的范围值, 其实很大了
- 两个兄弟的关系
- ![Netty支持的调优参数表4](pic/Netty支持的调优参数表4.png)
- 功能上可以细分为3类
- ![Netty支持的调优参数表5](pic/Netty支持的调优参数表5.png)
- ![Netty支持的调优参数表6](pic/Netty支持的调优参数表6.png)
- ![Netty支持的调优参数表7](pic/Netty支持的调优参数表7.png)
- 服务端调优 com.baiye.case5.server.ServerV1
- 客户端调优 com.baiye.case5.client.OrderClientV4
---
- SO_REUSEADDR
- 一般不会开启这个参数
- 地址重用参数
- ![地址重用参数](pic/地址重用参数.png)
- SO_LINGER
- 一般不会开启这个参数
- ![SO_LINGER参数](pic/SO_LINGER参数.png)
- ALLOW_HALF_CLOSURE
- 半关参数
- 一般不会开启这个参数
- ![ALLOW_HALF_CLOSURE参数](pic/ALLOW_HALF_CLOSURE参数.png)
```shell
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
```
- SocketChannel -> .childOption
- ServerSocketChannel -> .option
### 5.2 跟踪诊断 应用易诊断 | 可视化 | 内存不泄露
- **"易" 诊断**
- 完善 "线程名"
- 完善 "Handler" 名称
- 使用好 Netty 日志
- ![完善 "线程名"](pic/完善线程名.png)
- 一般实现 2-1 表示 boss group
- 一般实现 3-1 表示 worker group
- 以后可能改变
- ![完善Handler名称](pic/完善Handler名称.png)
- "$1" 表示一个匿名内部类
- "#0" 防止一个pipeline中加入多个 handler
- Netty 日志原理及使用
- Netty 日志框架原理
- Netty 会自动去依赖主流的日志框架的实现, 把 slf4j log4j 的 <option>ture 进行设置 所以可以直接使用
- 修改 JDK logger 级别
- 默认 JDK 的日志没有 "DEBUG" 的日志级别, 默认是使用 "FINE"
- 使用 slf4j + log4j 示例
- 加入相关的 日志依赖即可
- 衡量好 longing handler 的位置和级别
- 参考代码 com.baiye.case5.server.ServerV1
- **可视化**
- 演示如何做 Netty 可视化
- 实现一个: 统计并展示当前系统连接数
- Console 日志定时输出
- JMX实时展示
- ~~ELKK TIG etc~~ (耗费时间, 但是是可视化图表)
- 参考 : com.baiye.case5.server.handler.MetricsHandler
- Netty 值得可视化的数据
- ![Netty值得可视化的数据](pic/Netty值得可视化的数据.png)
- ![Netty值得可视化的数据](pic/Netty值得可视化的数据1.png)
- ![Netty值得可视化的数据](pic/Netty值得可视化的数2.png)
- **内存不泄露**
- Netty 内存泄露指的是什么?
- 原因: "忘记" release
- ByteBuf buffer = ctx.alloc().buffer();
- ~~buffer.release() / ReferenceCountUtil.release(buffer);~~
- 后果: 资源未释放 -> OOM
- 堆外: 未 free (PlatformDependent.freeDirectBuffer(buffer));
- 池化: 未归还 (recyclerHandle.recycle(this))
- Netty 内存泄露检测核心思路
- 引用计数 (buffer.refCnt()) + 弱引用 (Weak reference)
- 引用计数
- 判断历史人物到底功大于过, 还是过大于功?
- 功 + 1, 过 -1 , =0时: 资源就该释放了
- 那什么时候判断? "盖棺定论" 时 -> 对象被 GC 后
- ![强引用和弱引用](pic/强引用和弱引用.png)
- ![Netty内存检测的核心](pic/Netty内存检测的核心.png)
- Netty 内存泄露检测的源码解析
- io.netty.util.ResourceLeakDetector
- ![Netty内存检测的核心](pic/Netty内存检测的核心1.png)
- 示例: 用 Netty 内存泄露检测工具做检测
- ![Netty内存检测的核心](pic/Netty内存检测的核心2.png)
### 5.3 优化使用
#### A. 用好 Netty 自带注解
- @Sharable
- 标识 handler 提醒可共享, 不标记共享的不能重复加入 pipeline, 会造成程序的中断
- @Skip
- 跳过 handler 的执行
- 4.x 不让使用, 因为直接把代码可以进行删除, 没必要通过比偶记得方式进行
- 但是他自己还是私人调用
- @UnstableApi
- 提醒不稳定, 慎用, 一般对未进行正式测试的类进行标记,不要进行使用
- @SuppressJava6Requirement
- ![去除Java6的报警注解](pic/去除Java6的报警注解.png)
- ![去除Java6的报警注解1](pic/去除Java6的报警注解1.png)
- ![去除Java6的报警注解2](pic/去除Java6的报警注解2.png)
- @SuppressForbidden
- ![去除报警注解](pic/去除报警注解.png)
- ![去除报警注解1](pic/去除报警注解1.png)
- ![去除报警注解1](pic/去除报警注解2.png)
#### B. 整改线程模型加快响应
- 业务常见的场景
- CPU密集型: 运算型
- IO密集型: 等待型
- CPU密集型
- ![CPU密集型](pic/CPU密集型.png)
- IO密集型
- ![IO密集型](pic/IO密集型.png)
#### C. 增强写, 延迟和吞吐量的抉择
- 写的 "问题"
- ![写的问题](pic/写的问题.png)
- 增加一点点延迟, 减少 flush 次数
- 改进方式1 : channelReadComplete
- ![channelReadComplete改进写](pic/channelReadComplete改进写.png)
- ![channelReadComplete改进写问题](pic/channelReadComplete改进写问题.png)
- 改进方式2 : flushConsulidationHandler
- ![flushConsulidationHandler源码分析](pic/flushConsulidationHandler源码分析.png)
#### D. 流量整形, 让其运行平稳
- 流量整形的用途
- 网盘限速(有意)
- 景点限流(无奈)
- Netty 内置的三种流量整形
- Channel 级别 io.netty.handler.traffic.ChannelTrafficShapingHandler
- Global 级别 io.netty.handler.traffic.GlobalTrafficShapingHandler
- Netty 流量整形的源码分析和总结
- ![流量整形的源码总结](pic/流量整形的源码总结.png)
- 示例: 流量整形的使用
- ![流量整形的使用](pic/流量整形的使用.png)
- com.baiye.case5.server.ServerV4
#### E. 不同平台开启 native
- 如何开启 native
- ![如何开启native](pic/如何开启native.png)
- com.baiye.case5.server.ServerV5
- 源码分析 Native 库的加载逻辑
- ![native库的加载逻辑](pic/native库的加载逻辑.png)
- 常见问题
- ![native库的加载逻辑](pic/native库的加载逻辑1.png)
- ![native库的加载逻辑](pic/native库的加载逻辑2.png)
### 5.4 安全增强
#### A. 设置 "水位线" 保护自己安全
- Netty OOM 的根本原因
- ![OOM的根本原因](pic/OOM的根本原因.png)
- Netty OOM - ChannelOutboundBuffer
- ![ChannelOutboundBuffer-OOM](pic/ChannelOutboundBuffer-OOM.png)
- Netty OOM - TrafficShapingHandler
- ![TrafficShapingHandler-OOM](pic/TrafficShapingHandler-OOM.png)
- ![unwritable-OOM](pic/unwritable-OOM.png)
- Netty OOM 的对策
- ![OOM-对策](pic/OOM-对策.png)
- com.baiye.case5.server.handler.OrderServerProcessHandler
#### B. 启动空闲检测
- ![启动空闲检测](pic/启动空闲检测.png)
- Server : com.baiye.case5.server.handler.ServerIdleCheckHandler
- Cliet : com.baiye.case5.client.handler.ClientIdleCheckHandler [编码前]| com.baiye.case5.client.handler.KeepaliveHandler [编码后]
- PS: 记得把整个 Handler 放到 Client 中
#### C. 黑白名单
- Netty 中的 "cidrPrefix" 是什么?
- ![cidrPrefix](pic/cidrPrefix.png)
- Netty 地址过滤功能源码分析
- 同一个 Ip 只能有一个连接
- Ip 地址过滤: 黑名单, 白名单
- 示例: 使用黑名单增强安全
- com.baiye.case5.server.ServerV7
#### D. 自定义授权
- com.baiye.case5.server.handler.AuthHandler [编码后]
- 对应Client端必须把整个消息进行第一个发送出去
#### E. SSL 设置
## 成就点
- 负责公司千万级别实时在线用户长连消息推送系统
- 企业社交 Sass 完美解决方案
## 主要方案
- 完美应用示例(十万级): 线程池异步业务 + (Linux 句柄) + 单机Netty + 日志
- 异步化完美方案(百万级): 线程池异步业务 + (Linux 句柄) + HA高可用 + 日志
- 多服务端解决方案(千万级): Disruptor异步业务 + (Linux 句柄) + HA高可用 + 日志
## 参考资料
- https://www.imooc.com/t/6224286 慕课网牛人教程
-
## Netty基本组件
- Netty对传统 Socket 进行封装
- NioEventLoopChannelByteBufPipelineChannelHandler
![Netty组件对应](pic/Netty组件对应关系.png)
- NioEventLoop => Thread(监听客户端的连接 + 处理每个连接的读写线程)
- Channel => Socket 对一条连接的封装
- ByteBuf => IO Bytes 每一个Channel上数据流的处理
- Pipeline => Logic Chain 逻辑处理链
- Channel Handler => Logic 每一个逻辑
## Netty服务端启动
- 分析服务端启动流程包括服务端Channel的创建初始化以及注册到selector
- 服务端的启动在哪里初始化?
- 在哪里accept连接?
- 启动过程
- 创建服务端 Channel -> newChannel()
- 初始化服务端 Channel -> init()
- 注册 selector -> register()
- 端口绑定 -> doBind()
---
- 源码分析1 - 创建服务端Channel
- bind()[用户代码入口] -> initAndRegister()[初始化并注册] -> newChannel()[创建服务端channel]
- 反射创建服务端Channel : NioServerSocketChannel()[构造函数] -> newSocket()[通过jdk来创建底层jdk channel] -> NioServerSocketChannelConfig()[tcp参数配置类] -> AbstractNioChannel() ->
- configureBlocking(false)[阻塞模式] -> AbstractChannel()[创建id, unsafe, pipeline]
- 源码分析2 - 初始化服务端Channel
- init()[初始化入口] -> set ChannelOptions, ChannelAttrs [这个配置用的不多] -> set ChildOptions,ChildAttrs [每次创建的新连接都会把这两个属性配置上去] -> config handler[配置服务端pipeline] ->
- add ServerBootstrapAcceptor[添加连接器,默认添加特殊的Handler]
- 源码分析3 - 注册 selector
- AbstractChannel.register(channel)[入口] -> this.eventLoop = eventLoop[绑定线程] -> register0()[实际注册] -> doRegister()[调用jdk底层注册] -> invokeHandlerAddedIfNeeded()[做一些事件的回调] ->
- fireChannelRegistered()[传播事件]
- 源码分析4 - 端口绑定
- AbstractUnsafe.bind()[入口] -> doBind() -> javaChannel().bind()[jdk底层绑定] -> pipeline.fireChannelActive()[传播事件] -> HeadContext.readIfIsAutoRead()
## NioEventLoop
- 分析Netty reactor 线程处理过程,包括事件监听,事件处理,常规任务处理和定时任务处理
- 问题:
- 默认情况下, Netty 服务端启多少线程? 何时启动?
- Netty是如何解决jdk空轮询Bug的?会导致CPU飙升到100%
- Netty是如何保证异步串行无锁化?
---
- NioEventLoop 创建
- new NioEventLoopGroup()[线程组, 默认2*CPU] -> new ThreadPerTaskExecutor()[线程创建器] -> for(){new Child()}[循环构造NioEventLoop对象数组] ->
- chooserFactory.newChooser()[线程选择器:给每个新连接分配NioEventLoop线程]
- 深入剖析 ThreadPerTaskExecutor()
- 每次执行任务都会创建一个线程实体 -> FastThreadLocalThread
- NioEventLoopGroup 线程命名规则 nioEventLoop-1-xx
- 深入剖析 newchild()
- 保存线程执行器 ThreadPerTaskExecutor
- 创建一个 MpscQueue
- 创建一个 selector
- 深入剖析 chooserFactory.newChooser() 创建线程选择器
- chooserFactory.newChooser() -> isPowerOfTwo()[判断是否是2的幂, 如2,4,8,16] ->[TRUE] PowerOfTwoEventExecutorChooser[优化] -> index++ & (length-1) ->
- [FALSE] GenericEventExecutorChooser[普通] -> abs(index++ % length)
- ps: 计算机底层 & 操作比 取模操作高效的多, &操作是二进制的操作
---
- NioEventLoop 启动触发器
- 服务端启动绑定端口
- 新连接接入通过 chooser 绑定一个 NioEventLoop
- 启动流程
- bind() -> execute(task)[入口] -> startThread() -> doStartThread()[创建线程] -> ThreadPerTaskExecutor.execute()[线程执行器进行创建] ->
- thread = Thread.currentThread()[保存创建的线程] -> NioEventLoop.run()[启动]
---
- NioEventLoop 执行逻辑
- NioEventLoop.run() -> for(;;) -> select()[检查是否有io事件] -> processSelectedKeys()[处理io事件] -> runAllTasks()[处理异步任务队列]
---
- select() 方法执行逻辑 - 检测IO事件
- deadline 以及任务穿插逻辑处理
- 阻塞式的 select
- 解决 jdk 空轮训的 bug
## 新连接接入
- 分析新连接接入以及绑定reactor线程绑定到selector的过程
## pipeline
- 分析pipeline的创建初始化添加和删除ChannelHandler事件传播机制异常传播机制
## ByteBuf
- 详细分析ByteBuf种类如何减少多线程内存分配竞争不同大小内存是如何分配的
## Netty解码
- 详细分析Netty解码原理解码器抽象以及几种常见的解码器
## Netty编码及writeAndFlush()
- writeAndFlush传播流程编码器抽象writeAndFlush详细流程
## Netty性能优化工具类解析
- 详细分析Netty里面最高频使用的两个性能优化类FastThreadLocal以及轻量级对象池Recycler
## Netty设计模式应用
- 分析各类常见设计模式以及在Netty中的应用
## Netty高性能并发调优
- 系统层面单机如何支持百万连接,如何提升应用层面性能
- 单机通过修改Linux句柄数目限制提升
- 应用层主要是通过线程池和异步方式进行调优
## Netty调优
## 实战及应用
- 开源软件中的应用
- 实现 RPC
## 数据可靠性通信场景分析与架构设计 - 实际场景
- 数据通信要求实时性高, 且高性能, 异构系统
- 需要保障不同的业务请求对应不同的实现
- 支持负载均衡策略(TCP级别的), 故障切换
- 需要可靠性保障的支持, 数据不允许丢失
![高可靠架构](pic/高可靠性架构设计分析.png)
- 定时任务是对中间状态进行定时的扫描, 进行消息推送补偿, 改写为最终状态的
### 1. 思考的问题
- 怎么定义数据结构?
- 怎么做整合, 比如和 Spring 进行整合使用?
- 怎么让 Netty 对不同的业务有不同的实现?
![Netty对不同的请求进行不同的Server处理](pic/Netty对不同的请求进行不同的Server处理.png)
### 2 自定义数据格式 - 定义使用的数据结构
- Message 的格式是通用的
- 只需要把业务相关的数据格式进行定义即可,就是对应的什么模型类
### 3 整合 SpringBoot - 思路
### 4. 高可用 Netty 架构分析
![Netty高可用架构](pic/Netty架构设计.png)
- 常见的负载均衡策略 : LBS / Haproxy + Keepalived / Nginx
- 选择最小连接数的策略
## Netty调优
### Netty调优 - 内存调优
#### 1. netty客户端连接池泄漏问题复现及原因解析
- 问题复现: 生产环境使用 netty 作为客户端通信框架, 在客户端创建一个 tcp 连接池, 随着也无压力的上升, 在高峰期出现OOM问题, 需要重启才能恢复
- 参考: com.baiye.case2
- Bootstrap 不是线程安全的
- 真正的逻辑是 bossGroup 来进行处理的
- **总结**:编码规范问题, 注意客户端连接建立的编写, 关闭chanel的问题
### 2. netty内存池泄漏问题复现及排查解析
- 问题复现: 服务端使用 netty 作为通信框架, 负责消息接入和路由转发, 在功能测试时没有发现问题, 转性能测试后, 运行一段时间发现内存分配异常, 服务端无法接收请求消息, 系统吞吐量为0
- 当消息进来的时候, Netty给他分配的内存没有释放,最终导致了内存泄漏
- ByteBuf申请和释放场景分析
- 基于内存池的请求 ByteBuf
- 我们建议在使用 ServerHandler 时候实际使用 SimpleChannelInboundHandler , 不会存在内存泄露问题
- 基于非内存池请求的 ByteBuf (不推荐), 还是要自己释放
- 基于内存池的响应 ByteBuf
- 基于非内存池的响应 ByteBuf (不推荐) 还是要自己释放
- **总结**: ServerHandler 选用 SimpleChannelInboundHandler 来规避内存泄漏问题,但是仅限于你的Handler里面是同步处理的逻辑, 如果是异步的处理, 只能用 ChannelInboundHandlerAdapter
### 3. netty 内存池 的性能对比
- 4.x 引入 内存池机制, 对netty的提升很大
- com.baiye.case3.ByteBufPerformance 池化代码测试
- netty 内存池很大程度上提升了系统性能, 但是无用则会代理内存泄漏问题, 由于内存的申请和释放可能由Netty框架隐形完成, 所以增加了内存管理复杂度,所以必须深入理解ByteBuf的申请和释放机制,以免误用
- **总结**: 使用 4.x 提供的池化技术 PooledByteBufAllocator , 提升性能
### 4. ByteBuf 故障排查及优化 - HTTP 响应 Body 获取异常
- HTTP协议栈 ByteBuf 使用问题
- netty 解决好 HTTP 协议的问题, 就可以当 Tomcat 进行使用
### Netty调优 - 并发调优