[文档修改](master): Netty 学习笔记

更新了周末学习的成果 2022-08-22 00:46:05
master
土豆兄弟 2 years ago
parent 56ef3ccacc
commit 26ccc04ee0

@ -3,11 +3,50 @@
## 目录 ## 目录
Linux 内核技术 - 熟练使用 Linux 命令行
- 《鸟哥的Linux私房菜》
- 《Linux系统管理技术手册》
- 掌握 Linux 程序设计
- 《Unix 环境高级编程》
- 了解 Linux 内核机制
- 《深入理解Linux内核》
- 阅读 Linux 内核源码
- 《Linux内核源代码情景分析》
- 实验定制化Linux组件
- Linux 调优
- Linux 实战
## Linux 命令行
### 1.
Linux 性能优化
Linux 实战
## 2. Linux调优与架构优化 ## 2. Linux调优与架构优化

@ -923,6 +923,19 @@ print('type of original_params = {}, original_params = {}'.format(type(original_
### 5. 修炼基本功:条件与循环 ### 5. 修炼基本功:条件与循环
### 6. 异常处理:如何提高程序的稳定性? ### 6. 异常处理:如何提高程序的稳定性?

@ -1,5 +1,299 @@
# Netty # 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 [他自己的场景来建立的]
## 成就点 ## 成就点
- 负责公司千万级别实时在线用户长连消息推送系统 - 负责公司千万级别实时在线用户长连消息推送系统
- 企业社交 Sass 完美解决方案 - 企业社交 Sass 完美解决方案

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

Loading…
Cancel
Save