Netty
2022-06-14 23:08:46 3 举报
AI智能生成
Netty
作者其他创作
大纲/内容
Netty
资料
https://zhuanlan.zhihu.com/p/85140317
https://www.processon.com/view/5ea7e4437d9c0869daad8f41?fromnew=1#map
网络IO基础知识
1.OSI 七层网络
2.TCP和UDP
UDP是面向无连接的通讯协议,UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象。
3.Socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口
4.Linux IO模型
同步
1.阻塞io (blocking i/o)
2.非阻塞io (non blocking i/o)
3.IO多路复用 (mulitplexing i/o)
最大连接数
select
单个进程的最大连接数受FD_SETSIZE限制
poll
没有最大连接数限制
epoll
FD剧增带来的io效率
FD增加会造成遍历速度慢的线性下降性能问题
同上
消息传递方式
内核将消息传递到用户空间
内核和用户空间共享一块内存
4.信号驱动 (signal driven i/o)
异步
1.异步io (asyncchronous i/o)
5.应用程序如何获取数据
数据准备
数据拷贝
Netty概述
怎么介绍一种技术
1. 应用维度
Java多线程的产生,是因为要并发,并发使得程序的多种功能能响应更快,用户体验更好
在 Java 世界里,你可以实现 Runnable 接口、扩展 Thread类来实现代码并发;同时,Java 提供 synchronized 关键字,以及各种锁,来帮你控制并发中的代码行为和衍生问题。这需要了解接口和关键字的使用规则和潜在影响,以及各功能的细微差别。比如, sleep() 和 wait() 的区别是什么, 为什么 wait() 需要在同步代码块内使用,而 sleep() 不需要
在多线程场景下实现锁来确保线程的同步,但是加锁、释放锁是个非常消耗资源的操作,没有获得锁的线程还需要进入阻塞状态,等待被唤醒。 如果多个线程的竞争并不激烈,可以考虑使用 CAS 的方式实现无锁的线程同步,线程可以一直运行,不用阻塞。但是使用 CAS 还需要考虑使用时间戳等方式来解决 ABA 问题
对于 Java,从低并发逐渐发展到高并发,如何充分利用系统的能力,减少响应时间变得非常重要
问题
从技术的应用维度看,首先考虑的是要解决什么问题,这是技术产生的原因
问题这层,用来回答“干什么用
技术规范
接下来,技术被研发出来,人们怎么用它才能解决问题呢?这就要看技术规范,可以理解为技术使用说明书
技术规范,回答“怎么用”的问题,反映你对该技术使用方法的理解深度。
最佳实践
你把该技术运用到多种不同的场景时,会发现同样的使用方法,会有不同的效果,这是因为问题上下文不同了,该技术有不同的适应面。
最佳实践回答“怎么能用好”的问题,反映你实践经验的丰富程度
市场应用趋势
随着技术生态的发展,和应用问题的变迁,技术的应用场景和流行趋势会受到影响
这层回答“谁用,用在哪”的问题,反映你对技术应用领域的认识宽度。
2. 设计维度
比如,Java 多线程要在优先级调度、锁、信息同步等方面达成怎样的目标,才能更好地实现并发
Java 多线程的实现原理包括内核线程、使用用户态线程、使用用户态线程加轻量级进程混合等部分,还包括硬件指令集、Test and Set、各种锁等
比如在 Java 多线程编程中,采用共享内存的方式,锁的开销比较大,程序员编程难度较大,容易出错,难以调试
目标
为了解决用户的问题,技术本身要达成什么目标
这层定义“做到什么”
实现原理
为了达到设计目标,该技术采用了什么原理和机制
实现原理层回答“怎么做到”的问题
优劣局限
每种技术实现,都有其局限性,在某些条件下能最大化的发挥效能,缺少了某些条件则暴露出其缺陷
优劣局限层回答“做得怎么样”的问题。
演进趋势
技术是在迭代改进和不断淘汰的。了解技术的前生后世,分清技术不变的本质,和变化的脉络,以及与其他技术的共生关系,能体现你对技术发展趋势的关注和思考。
这层体现“未来如何”。思考相关生态,未来发展,横向纵向对比
Netty是什么?
网络应用程序框架
开发服务器与客户端程序
特性总结
设计
统一的API,支持多种传输类型,阻塞的和非阻塞的简单而强大的线程模型真正的无连接数据报套接字支持链接逻辑组件以支持复用
易用使用
详实的Javadoc和大量的示例集不需要超过JDK 1.6+ [7] 的依赖。(一些可选的特性
性能
拥有比Java的核心API更高的吞吐量以及更低的延迟得益于池化和复用,拥有更低的资源消耗最少的内存复制
健壮性
不会因为慢速、快速或者超载的连接而导致OutOfMemoryError 消除在高速网络中NIO应用程序常见的不公平读/写比率
安全性
完整的SSL/TLS以及StartTLS支持可用于受限环境下,如Applet和OSGI
社区驱动
发布快速而且频繁
优劣势局限
优势
异步,事件驱动
高性能,可维护,快速开发
支持常用应用层协议
解决传输问题:粘包,半包现象
支持流量整形
完善的断链,idle等异常处理等
为什么用Netty而不用Java的NIO
规避了JDK NIO bug
经典额epoll bug:异常唤醒空转导致CPU 100%
IP_TOS参数使用抛出异常
比JDK API跟友好更强大
JDK的NIO的一些API不够友好,功能薄弱,例如ByteBuffer->Netty's ByteBuf
除了NIO外,也提供了其他一些增强:ThreadLocal->Netty's FastThreadLocal
Nett隔离了变化,屏蔽了细节
隔离JDK NIO的实现变化:nio->nio2(aio)
屏蔽了JDK NIO的实现细节
与其他框架对比
Sun的Grizzly
Tomcat/jetty
Cindy
Apple SwfitNIO
Mina
支持更多的流行协议
Netty历史
从归属组织上看
版本演进
netty现状
社区现状
维护者
分支
建议使用4.1
应用现状
使用netty的典型项目
Netty基础技术原理
Netty对I/O模型的支持
Linux IO模型
有最大连接数限制数为1024,单个进程所能打开的最大连接数由FD_ZETSIZE宏定义
poll本质上与select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。
虽然连接有上限,但是很大,1G内存的机器可以打开10万左右的连接,以此类推
监听Socket的方式不一致
轮询的方式,一个一个的socket检查过去,发现有socket活跃时才进行处理,当线性socket增多时,轮询的速度将会变得很慢,造成线性造成性能下降问题。
对select稍微进行了优化,只是修改了文件描述符,但是监听socket的方式还是轮询
epoll的内核和用户空间共享一块内存,因此内存态数据和用户态数据是共享的
I/O模型介绍
同步阻塞模式
BIO是同步阻塞模型,一个客户端连接对应一个处理线程。在BIO中,accept和read方法都是阻塞操作,如果没有连接请求,accept方法阻塞;如果无数据可读取,read方法阻塞。
图解
所以需要多线程以实现多任务处理
多路复用
利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。
子主题
Reactor模式
三种版本
Thread per connection
多线程模式
单线程模式
主从多线程模式
Netty实现
Netty线程模型
Netty对三种I/O模型的支持
BossGroup
作用
管理NioEventLoop的生命周期
BossGroup用来接受客户端发来的连接
组成
NioEventLoop
一个NIO Selector
一个队列
一个线程
处理流程
将NioSocketChannel注册到某个worker NIOEventLoop上的selector。
处理任务队列的任务, 即runAllTasks。
WorkerGroup
WorkerGroup则负责对完成TCP三次握手的连接进行处理。
ChannelPipeline
轮询注册到自己Selector上的所有NioSocketChannel的read和write事件。
处理read和write事件,在对应NioSocketChannel处理业务。
runAllTasks处理任务队列TaskQueue的任务,一些耗时的业务处理可以放入TaskQueue中慢慢处理,这样不影响数据在pipeline中的流动处理。
Netty体系结构
Netty领域对象
回调
Channel处理流程
Channel与ChannelPipeline关系
在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应
逻辑
Bootstrap
Bootstrap是创建客户端连接的启动器
ServerBootstrap是监听服务端端口的启动器
通道(Channel)
当前网络连接的通道的状态(例如是否打开?是否已连接?)
网络连接的配置参数 (例如接收缓冲区大小)
提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。
调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
支持关联 I/O 操作与对应的处理程序
Channel类型
NioSocketChannel,异步的客户端 TCP Socket 连接。
NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
NioDatagramChannel,异步的 UDP 连接。
NioSctpChannel,异步的客户端 Sctp 连接。
NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
Selector
Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。
当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel
EventLoop
NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:
任务类型
I/O 任务
即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。
非 IO 任务
添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。
NioEventLoopGroup
主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。
ChannelHandler
ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。
常见的类型
ChannelInboundHandler 用于处理入站 I/O 事件。
ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。
ChannelOutboundHandler 用于处理出站 I/O 操作。
ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。
ChannelDuplexHandler 用于处理入站和出站事件。
ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。
ChannelPipline
实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互
编码器和解码器
入站消息会被解码
从字节转换为另一种格式,通常是一个Java对象
如果是出站消息,则会发生相反方向的转换
常见的编解码器
ByteToMessageDecoder
MessageToByte-Encoder
ProtobufEncoder
ProtobufDecoder
Netty处理流程
服务端初始化启动
详细说明
流程概述
Channel创建
通过反射创建NioServerSocketChannel
将Java原生channel绑定到netty的channel中
注册Accept事件
给channel分配ID, 创建Unsafe和ChannelPipeline
Channel初始化
为Channel设置ServerBootstrapConfig属性
为Channel绑定的Pipeline添加ServerBootstrapAcceptor
Pipeline初始化
将Channel注册到Selector
EventLoop初始化
添加Handle
轮询客服端的请求
客服端启动流程
服务端数据处理流程
Boss NioEventLoop处理
1)轮询 Accept 事件;
2)处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;
3)处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。
Worker NioEventLoop处理
1)轮询 Read、Write 事件;
2)处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;
3)处理任务队列中的任务,runAllTasks。
Netty功能实现原理
Netty传输
Netty为它所有的传输实现提供了一个通用API
这个统一的封装便是Channel
Channel与EventLoop是多对1的关系
所有Channel是线程安全的
Netty实现不同传输举例
NIOio.netty.channel.socket.nio使用java.nio.channels 包作为基础——基于选择器的方式
Epoll [2]io.netty.channel.epoll由JNI驱动的epoll() 和非阻塞IO。这个传输支持只有在Linux上可用的多种特性,如SO_REUSEPORT ,比NIO传输更快,而且是完全非阻塞的
OIOio.netty.channel.socket.oio使用java.net 包作为基础——使用阻塞流
Localio.netty.channel.local可以在VM内部通过管道进行通信的本地传输
Embeddedio.netty.channel.embeddedEmbedded传输,允许使用ChannelHandler 而又不需要一个真正的基于网络的传输。这在测试你的ChannelHandler 实现时非常有用
Channel的生命周期
ChannelUnregistered
Channel 已经被创建,但还未注册到EventLoop
ChannelRegistered
Channel 已经被注册到了EventLoop
ChannelActive
Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了
ChannelInactive
Channel 没有连接到远程节点
总结
Netty内存管理
ByteBuf概述
说明
ByteBuf主要是通过两个指针进行数据的读和写,分别是 readerIndex 和 writerIndex ,并且整个ByteBuf被这两个指针最多分成三个部分,分别是可丢弃部分,可读部分和可写部分,可以用一张图直观的描述ByteBuf的结构
ByteBuf API的优点
它可以被用户自定义的缓冲区类型扩展;
通过内置的复合缓冲区类型实现了透明的零拷贝;
容量可以按需增长(类似于JDK的StringBuilder );
在读和写这两种模式之间切换不需要调用ByteBuffer 的flip() 方法;
读和写使用了不同的索引;
支持方法的链式调用;
支持引用计数;
支持池化。
ByteBuf结构
ByteBuf种类
为什么使用ByteBuf替换JDk的ByteBuffer
ByteBuffer长度固定,不能扩容
ByteBuffer只要一个position不易读写
ByteBuff使用模式
堆缓冲区模式
数据存放在JVM的堆空间,通过将数据存储在数组中实现
堆缓冲的优点: 由于数据存储在Jvm堆中可以快速创建和快速释放,并且提供了数组直接快速访问的方法
堆缓冲的缺点: 每次数据与I/O进行传输时,都需要将数据拷贝到直接缓冲区
直接缓冲区模式
于堆外分配的直接内存,不会占用堆的容量。适用于套接字传输过程,避免了数据从内部缓冲区拷贝到直接缓冲区的过程,性能较好
优点: 使用Socket传递数据时性能很好,避免了数据从Jvm堆内存拷贝到直接缓冲区的过程。提高了性能
缺点: 相对于堆缓冲区而言,Direct Buffer分配内存空间和释放更为昂贵
复合缓冲区模式
本质上类似于提供一个或多个ByteBuf的组合视图,可以根据需要添加和删除不同类型的ByteBuf
ByteBuf Api说明
5.3.1 随机访问索引
5.3.2 顺序访问索引
5.3.3 可丢弃字节
5.3.4 可读字节
5.3.5 可写字节
5.3.6 索引管理
5.3.9 读/写操作
get() 和set() 操作,从给定的索引开始,并且保持索引不变;
read() 和write() 操作,从给定的索引开始,并且会根据已经访问过的字节数对索引进行调整。
ByteBufHolder
ByteBuf分配
按需分配:ByteBufAllocator接口
返回一个基于堆或者直接内存存储的ByteBuf
返回一个基于堆内存存储的ByteBuf
返回一个基于直接内存存储的ByteBuf
compositeBuffer() compositeBuffer(int maxNumComponents) compositeDirectBuffer() compositeDirectBuffer(int maxNumComponents); compositeHeapBuffer() compositeHeapBuffer(int maxNumComponents);
返回一个可以通过添加最大到指定数目的基于堆的或者直接内存存储的缓冲区来扩展的CompositeByteBuf
ioBuffer() [8]
返回一个用于套接字的I/O操作的ByteBuf
ByteBufAllocator实现
PooledByteBufAllocator
前者池化了ByteBuf 的实例以提高性能并最大限度地减少内存碎片。此实现使用了一种称为jemalloc [9] 的已被大量现代操作系统所采用的高效方法来分配内存
Unpooled-ByteBufAllocator
后者的实现不池化ByteBuf 实例,并且在每次它被调用时都会返回一个新的实例。
Netty处理器
ChannelHandler概述
ChannelHandler的生命周期
handlerAdded当把ChannelHandler 添加到ChannelPipeline 中时被调用
handlerRemoved当从ChannelPipeline 中移除ChannelHandler 时被调用
exceptionCaught当处理过程中在ChannelPipeline 中有错误产生时被调用
ChannelHandler层次结构
ChannelInboundHandler ——处理入站数据以及各种状态变化;
ChannelOutboundHandler ——处理出站数据并且允许拦截所有的操作。
ChannelHandler分类
ChannelInboundHandler接口
SimpleChannelInboundHandler
ChannelOutboundHandler接口
ChannelHandler适配器
资源管理
每当通过调用ChannelInboundHandler.channelRead() 或者ChannelOutbound- Handler.write() 方法来处理数据时,你都需要确保没有任何的资源泄漏。
Netty提供了class ResourceLeakDetector用来诊断潜在的资源泄漏问题
ChannelHandlerContext 代表了ChannelHandler 和ChannelPipeline 之间的关联,每当有ChannelHandler 添加到ChannelPipeline 中时,都会创建ChannelHandler- Context 。ChannelHandlerContext 的主要功能是管理它所关联的ChannelHandler 和在同一个ChannelPipeline 中的其他ChannelHandler 之间的交互。
EventLoop和线程模型
EventLoop结构图
任务调度
JDK的任务调度API
使用EventLoop调度任务
EventLoop/线程的分配
服务于Channel 的I/O和事件的EventLoop 包含在EventLoopGroup 中。根据不同的传输实现,EventLoop 的创建和分配方式也不同。
1.异步传输
异步传输实现只使用了少量的EventLoop (以及和它们相关联的Thread ),而且在当前的线程模型中,它们可能会被多个Channel 所共享。这使得可以通过尽可能少量的Thread 来支撑大量的Channel ,而不是每个Channel 分配一个Thread 。
阻塞传输
Netty如何支持Reactor模式
Netty引导
编解码器框架
编码和解码,或者数据从一种特定协议的格式到另一种格式的转换。这些任务将由通常称为编解码器的组件来处理。Netty提供了多种组件,简化了为了支持广泛的协议而创建自定义的编解码器的过程。例如,如果你正在构建一个基于Netty的邮件服务器,那么你将会发现Netty对于编解码器的支持对于实现POP3、IMAP和SMTP协议来说是多么的宝贵
一次编解码器
ByteToMessageDecoder接口
如TCP粘包半包处理
MessageToByteEncoder接口
ByteToMessageCodec
二次编解码器
MessageToMessageDecoder接口
Message到 Java对象
Java系列化
Marshaling
Xml
Json
MessagePack
ProtoBuf
选择的标准
编解码占用的空间
编解码占用的时间
对多语言的支持
MessageToMessageEncoder接口
MessageToMessageCodec
解码器
分类
将字节解码为消息——ByteToMessageDecoder 和ReplayingDecoder ;
将一种消息类型解码为另一种——MessageToMessageDecoder 。
场景的解码器
抽象类ByteToMessageDecoder
抽象类ReplayingDecoder
预置的ChannelHandler和编解码器
SslHandler
Http/Https处理
请求处理
响应处理
netty对应的handler
http
HttpRequestEncoder
将HttpRequest 、HttpContent 和LastHttpContent 消息编码为字节
HttpResponseEncoder
将HttpResponse 、HttpContent 和LastHttpContent 消息编码为字节
HttpRequestDecoder
将字节解码为HttpRequest 、HttpContent 和LastHttpContent 消息
HttpResponseDecoder
将字节解码为HttpResponse 、HttpContent 和LastHttpContent 消息
聚合HTTP消息
HttpObjectAggregator
Http压缩
HttpContentCompressor
编码实现
https
在http基础上添加SslHandler
WebSocket
它为网页和远程服务器之间的双向通信提供了一种替代HTTP轮询的方案
原理图
WebSocket帧类型
数据帧
BinaryWebSocketFrame
数据帧:二进制数据
TextWebSocketFrame
数据帧:文本数据
ContinuationWebSocketFrame
数据帧:属于上一个BinaryWebSocketFrame 或者TextWeb- SocketFrame 的文本的或者二进制数据
控制帧
CloseWebSocketFrame
控制帧:一个CLOSE 请求、关闭的状态码以及关闭的原因
PingWebSocketFrame
控制帧:请求一个PongWebSocketFrame
PongWebSocketFrame
控制帧:对PingWebSocketFrame 请求的响应
空闲的连接和超时
检测空闲连接以及超时对于及时释放资源来说是至关重要的。由于这是一项常见的任务,Netty特地为它提供了几个ChannelHandler 实现。表11-4给出了它们的概述。
管理的ChannelHandler
IdleStateHandler
当连接空闲时间太长时,将会触发一个IdleStateEvent 事件。然后,你可以通过在你的ChannelInboundHandler 中重写userEvent- Triggered() 方法来处理该IdleStateEvent 事件
ReadTimeoutHandler
如果在指定的时间间隔内没有收到任何的入站数据,则抛出一个Read- TimeoutException 并关闭对应的Channel 。可以通过重写你的ChannelHandler 中的exceptionCaught() 方法来检测该Read- TimeoutException
WriteTimeoutHandler
如果在指定的时间间隔内没有任何出站数据写入,则抛出一个Write- TimeoutException 并关闭对应的Channel 。可以通过重写你的ChannelHandler 的exceptionCaught() 方法检测该WriteTimeout- Exception
序列化
JDK序列化
CompatibleObjectDecoder [9]
和使用JDK序列化的非基于Netty的远程节点进行互操作的解码器
CompatibleObjectEncoder
和使用JDK序列化的非基于Netty的远程节点进行互操作的编码器
ObjectDecoder
构建于JDK序列化之上的使用自定义的序列化来解码的解码器;当没有其他的外部依赖时,它提供了速度上的改进。否则其他的序列化实现更加可取
ObjectEncoder
构建于JDK序列化之上的使用自定义的序列化来编码的编码器;当没有其他的外部依赖时,它提供了速度上的改进。否则其他的序列化实现更加可取
使用JBoss Marshalling进行序列化
通过Protocol Buffers序列化
使用protobuf对消息进行解码
使用protobuf对消息进行编码
ProtobufVarint32FrameDecoder
根据消息中的Google Protocol Buffers的“Base 128 Varints”a整型长度字段值动态地分割所接收到的ByteBuf
ProtobufVarint32LengthFieldPrepender
向ByteBuf 前追加一个Google Protocal Buffers的“Base 128 Varints”整型的长度字段值
Netty协议功能实现
原因
半包
1.可能是IP分片传输导致
2.传输过程中丢失部分包导致出现的半包
3.一个包可能被分成了两次传输,在取数据的时候,先取到了一部分
粘包
2.如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况
如何解决
固定长度
加分割符
消息头(消息体长度)
netty 自带解码器解决这个问题
FixedLengthFrameDecoder
LengthFieldPrepender
DelimiterBasedFrameDecoder
固定长度字段存个内容的长度信息
编码器
keepalive和idle监测
idle监测只是负责诊断,诊断后做出不同的行为
发送keepalive
关闭连接
netty怎么开启keepalive
netty怎么开启idle监测
netty实现UDP
Netty应用场景
包结构组织
0 条评论
回复 删除
下一页