Netty学习笔记
2023-11-07 17:21:51 0 举报
AI智能生成
Netty学习笔记
作者其他创作
大纲/内容
ChannelHandler
IO事件的处理类,类似于web应用中单个Filter、Servlet配置中的实际逻辑处理类
@Sharable
该注解表示一个ChannelHandler可以属于多个ChannelPipeline,保证线程安全
@Skip
被Skip注解的方法不会被调用,直接被忽略,直接跳到下一个ChannelHandler中执行对应的方法
子类
ChannelHandlerAdapter
选择性地拦截和处理某个或者某些事件
ChannelInitializer
初始化Server端新接入的SocketChannel对象
initChannel()
Channel被注册到EventLoop的时候会被调用
常用方法
生命周期方法
channelRegistered
当前channel注册到EventLoop
channelUnregistered
当前channel从EventLoop取消注册
channelActive
当前channel激活的时候
channelInactive
当前channel不活跃的时候,也就是当前channel到了它生命周期末
数据处理方法
channelRead
当前channel从远端读取到数据
channelReadComplete
channel read消费完读取的数据的时候被触发
userEventTriggered
用户事件触发的时候
channelWritabilityChanged
channel的写状态变化的时候触发
exceptionCaught
当发生异常时,关闭ChannelHandlerContext,释放和ChannelHandlerContext相关联的句柄等资源
常用系统ChannelHandler
ByteToMessageCodec
系统编解码框架
LengthFieldBasedFrameDecoder
通用基于长度的半包解码器
LoggingHandler
码流日志打印
SslHandler
SSL安全认证
IdleStateHandler
链路空闲检测
ChannelTrafficShapingHandler
流量整形
Base64Decoder和Base64Encoder
Base64编解码
AttributeMap
AttributeMap和Map的格式很像,key是AttributeKey,value是Attribute,我们可以根据AttributeKey找到对应的Attribute
Channel上的AttributeMap就是大家共享并且线程安全,每一个ChannelHandler都能获取到
ChannelHandlerContext上的AttributeMap是每个ChannelHandler独有的
AttributeKey
Attribute
set(value)
get()
ChannelOption
内存配置&线程池配置&TCP参数配置
SO_BACKLOG
对应的是tcp/ip协议listen函数中的backlog参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列
服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
CONNECT_TIMEOUT_MILLIS
客户端连接超时设置,默认值30000毫秒即30秒
SO_SNDBUF和SO_RCVBUF
对应于套接字选项中的SO_SNDBUF和SO_RCVBUF,这两个参数用于操作接收缓冲区和发送缓冲区的大小
接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功,发送缓冲区用于保存发送数据,直到发送成功
SO_KEEPALIVE
对应于套接字选项中的SO_KEEPALIVE,该参数用于设置TCP连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。
当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。
TCP_NODELAY
对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关
Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时
TCP_NODELAY参数的作用就是禁止使用Nagle算法,使用于小数据即时传输,于TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送
SO_REUSEADDR
对应于socket选项中的SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口
如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置SO_REUSEADDR就无法正常使用该端口
SO_LINGER
对应于套接字选项中的SO_LINGER,使用SO_LINGER可以阻塞close()的调用时间,直到数据完全发送
Linux内核默认的处理方式是当用户调用close方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证
会发生剩余的数据,造成了数据的不确定性
ChannelFuture
常用方法
channel()
sync()
将当前线程阻塞等待处理结果
get()、await()、cannel()
addListener(GenericFutureListener)
get的替代方案
removeListener(GenericFutureListener)
ChannelPromise
Promise是可写的Future,Future自身并没有写操作相关的接口,Netty通过Promise对Future进行扩展,用于设置I/O操作的结果
实现类
DefaultPromise
sync()
将当前线程阻塞等待处理结果
await()
setSuccess()、setFailure()
trySuccess()、tryFailure()
ByteBuf
用法和NIO的Buffer一样
相关辅助类
ByteBufHoler
ByteBufUtil
Buffer和ByteBuf对比
ByteBuffer长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当我们对ByteBuffer进行 put操作的时候,如果缓冲区剩余可写空间不够,就会发生 BufferOverflowException异常。
ByteBuf进行 put操作的时候会对剩余可用空间进行校验。如果剩余空间不足,需要重新创建一个新的 ByteBuffer,并将之前的 ByteBuffer复制到新创建的 ByteBuffer中,最后释放老的 ByteBuffer,
ByteBuffer只有一个标识位控的指针position,读写的时候需要手工调用flip()
ByteBuf通过两个位置指针来协助缓冲区的读写操作,读操作使用 readerIndex,写操作使用 writerIndex。随着数据的写入writerIndex会增加,读取数据会使 readerIndex增加,但是它不会超过 writerIndex。
服务器推送技术
Ajax短轮询
实现机制
用一个定时器不停的去网站上请求数据
优点
服务端基本不用改造
服务器内存较友好,不需要保持连接
缺点
服务器沉重压力和资源的浪费
数据同步不及时
Comet
实现机制
基于 HTTP长连接、无须在浏览器端安装插件的“服务器推”技术
基于 <font color="#e74f4c"><b>AJAX 的长轮询(long-polling)方式</b></font><br>
服务器端会阻塞请求直到有数据传递或超时才返回。
客户端JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。
应用
Spring带来的DeferedResult
Servlet3里的异步任务
服务器推送事件 Server-sent-events(SSE)
原理
严格地说,HTTP 协议无法做到服务器主动推送信息。<br>但是,有一种变通方法,就是<b><font color="#e74f4c">服务器向客户端声明,接下来要发送的是流信息(streaming)。<br>也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。<br>这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,</font></b>视频播放就是这样的例子。<br>本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
是 HTML 5 规范中的一个组成部分
SSE 是单向通道,只能服务器向客户端发送消息,如果客户端需要向服务器发送消息,则需要一个新的 HTTP 请求。
SSE适用于更新频繁、低延迟并且数据都是从服务端到客户端。
文本协议
优点
SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
SSE 默认支持断线重连,WebSocket 需要自己实现。
一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
SSE 支持自定义发送的消息类型。
WebSocket
原理
子主题
借用了HTTP的协议来完成一部分握手
实现比SSE负载,适用于需要进行复杂双向数据通讯的场景
二进制协议
特点
HTML5中的协议,实现与客户端与服务器双向通道,基于消息的文本或二进制数据通信
适合于对数据的实时性要求比较强的场景,如通信、直播、共享桌面,特别适合于客户与服务频繁交互的情况下,如实时共享、多人协作等平台。
采用新的协议,后端需要单独实现
客户端并不是所有浏览器都支持
全双工,可以进项双向的数据传输
简单(流)文本定向消息协议
STOMP
STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。是属于消息队列的一种协议, 和AMQP, JMS平级. 它的简单性恰巧可以用于定义websocket的消息体格式. STOMP协议很多MQ都已支持, 比如RabbitMq, ActiveMq。
生产者(发送消息)、消息代理、消费者(订阅然后收到消息)
STOMP是基于帧的协议
1. NIO基础<br>
1.IO 模型<br>
非阻塞 vs 阻塞<br>
强调进程/线程要访问的数据是否就绪,进程/线程是否需要等待,强调的数据准备这个过程;<br>
同步/异步<br>
同步和异步:同步和异步一般是面向操作系统与应用程序对IO操作的层面上来区别的。<br>
访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写,强调I/O真正读写这个阶段。<br>
阻塞 IO
非阻塞 IO<br>
多路复用<br>
异步 IO<br>
阻塞 IO vs 多路复用<br>
子主题
NIO vs BIO vs AIO
1.BIO(同步阻塞IO)<br>
数据的读取写入必须阻塞在一个线程内等待其完成<br>线程数量:客户端并发访问数为1:1,由于线程是Java虚拟机中非常宝贵的资源,一旦线程数急剧增加,系统性能会急剧下降,导致线程栈溢出,创建新的线程失败,并最终导致宕机
2.NIO(同步非阻塞IO)<br>
用NIO方式处理IO使用多路复用器Selector来轮询每个通道Channel,当通道中有事件时就通知处理,不过使用起来相当复杂。<br>
三大组件<br>
Channel
channel它就是读写数据的**双向通道**,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入<br>
Buffer(缓冲区)<br>
与Channel进行交互,数据是从Channel读入缓冲区,从缓冲区写入Channel中的<br>
Selector(选择器、多路复用器)<br>
3.AIO(真正的异步非阻塞IO)<br>
4.零拷贝<br>
传统 IO 问题<br>
流程<br>
NIO 优化
流程
序列化
影响序列化性能的关键因素
序列化后的码流大小(网络带宽的占用)
序列化的性能(CPU资源占用)
是否支持跨语言(异构系统的对接和开发语言切换)
xml方式
优点
人机可读性好,可指定元素或特性的名称
缺点
序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息
只能序列化公共属性和字段;不能序列化方法
文件庞大,文件格式复杂,传输占带宽
适用场景
当做配置文件存储数据,实时数据转换
JSON
优点
兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好、与XML相比,其协议比较简单,解析速度比较快
缺点
数据的描述性比XML差、不适合性能要求为ms级别的情况、额外空间开销比较大
适用场景
跨防火墙访问、可调式性要求高、基于Web browser的Ajax请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务
JDK Serializer
无法跨语言、序列化后的码流太大、序列化的性能差
不支持跨语言
FST
缺点
只争对Java,不跨语言
优点
序列化速度时JDK的10倍
序列化结果时JDK看的1/3<br>
语法及其简洁
kryo
基于protobuf协议,只支持java语言,需要注册(Registration),然后序列化(Output),反序列化(Input)
缺点
主要支持Java
优点
同FST样,使用简单,配置灵活
性能和FST相差不多
Fastjson
采用一种“假定有序快速匹配”的算法
优点
接口简单易用、目前java语言中最快的json库
缺点
过于注重快,而偏离了“标准”及功能性、代码质量不高,文档不全
适用场景
协议交互、Web输出、Android客户端
Thrift
不仅是序列化协议,还是一个RPC框架
优点
序列化后的体积小, 速度快、支持多种语言和丰富的数据类型、对于数据字段的增删具有较强的兼容性、支持二进制压缩编码
提供丰富的语言支持
缺点
使用者较少、跨防火墙访问时,不安全、不具有可读性,调试代码时相对困难、不能与其他传输层协议共同使用(例如HTTP)、无法支持向持久层直接读写数据,即不适合做数据持久化序列化协议
适用场景
分布式系统的RPC解决方案
Avro
Hadoop的一个子项目,解决了JSON的冗长和没有IDL的问题
优点
支持丰富的数据类型、简单的动态语言结合功能、具有自我描述属性、提高了数据解析速度、快速可压缩的二进制数据形式、可以实现远程过程调用RPC、支持跨编程语言实现
缺点
对于习惯于静态类型语言的用户不直观
适用场景
在Hadoop中做Hive、Pig和MapReduce的持久化数据格式
Protobuf
将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性
优点
序列化后码流小,性能高、结构化数据存储格式(XML JSON等)、通过标识字段的顺序,可以实现协议的前向兼容、结构化的文档更容易管理和维护
缺点
需要依赖于工具生成代码、支持的语言相对较少,官方只支持Java 、C++ 、python、Go、C#
适用场景
对性能要求高的RPC调用、具有良好的跨防火墙的访问属性、适合应用层对象的持久化
protostuff
基于protobuf协议,但不需要配置proto文件,直接导包即可
Jboss marshaling
可以直接序列化java类, 无须实java.io.Serializable接口
Message pack
一个高效的二进制序列化格式
Hessian
采用二进制协议的轻量级remoting onhttp工具
选择Netty的理由<br>
NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。<br>而Netty使用启动器封装了这些复杂的操作
NIO处理多条链路线程模型需要自己实现,Netty有高效的Reactor线程模型
IO线程处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大
处理网络的闪断、客户端的重复接入、客户端的安全认证、消息的编解码、半包读写
功能强大,预置了多种编解码功能,支持多种主流协议;
灵活的TCP参数配置能力
Netty提供了自己的Channel和其子类实现,不受JVM厂家标准限制,扩展性强
Netty为了解决ByteBuffer的缺陷,重写了一个新的数据接口ByteBuf,新增了高级和实用的特性
定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时更多的新功能会加入;
网络事件
典型的网络事件
链路注册、链路激活、链路断开、链路发生异常
接收到请求消息、请求消息接收并处理完毕
发送应答消息
发生用户自定义事件
Netty事件
事实上inbound和outbound事件是Netty自身根据事件在pipeline中的流向抽象出来的术语,事件在pipeline中得到传播和处理,它是事件处理的总入口。
inbound事件
通常由I/O线程触发,例如TCP链路建立事件、链路关闭事件、读事件、异常通知事件等
Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层。
Inbound事件发起者是unsafe,默认的处理者是 TailContext, 并且其处理方法是空实现
Inbound 事件在 Pipeline 中传输方向是 headContext -> tailContext
outbound事件
通常是由用户主动发起的网络I/O操作,例如用户发起的连接操作、绑定操作、消息发送等操作
Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求)
Outbound 事件的发起者是 Channel,Outbound 事件的处理者是 unsafe
Outbound 事件在 Pipeline 中的传输方向是 tailContext -> headContext
高性能
IO线程模型
2种fd
listenfd
一般情况只有一个,用来监听一个特定的端口(如8080)
connfd
每个连接都会打开一个connfd,用来收发数据
3种事件
listenfd进行accept阻塞监听,创建一个connfd
用户态内核态之间copy数据。每个connfd对应着2个应用缓冲区:readbuf和writebuf
处理connfd发来的数据,进行业务逻辑处理,准备response到writebuf
客户端请求的5个阶段
read
接收到请求,读取数据
decode
解码数据
compute
业务逻辑处理
encode
返回数据编码
send
发送数据
其中,以read和send阶段IO最为频繁
Reactor
Reactor模型基于事件驱动,来了事件我通知你,你来处理
三种角色
Reactor
事件监听和响应主线程,通过Select监控客户端请求事件,收到事件后进行dispatch分发
如果是连接事件,由acceptor线程接受连接
如果是读写事件,分发给事件绑定函数Handler处理
Acceptor
接受客户端新连接,创建新的SocketChannel将其注册到Reactor主线程的selecor上,并创建事件响应函数handler处理后续事件
Handler
事件响应函数,完成channel的读取数据,完成处理业务逻辑后,负责将结果写入channel发送给客户端
可用资源池来管理
三种Reactor线程模型
单Reactor单线程模型
Reactor单主线程所有事件的响应,如果是连接事件分发给Acceptor线程处理,如果是读写事件则Reactor单主线程完成整个Handler处理
理论上一个Reactor主线程可以独立处理所有IO相关的操作,但一个NIO线程同时处理成百上千的链路,会导致该线程负载过重,处理速度将变慢,性能上无法支撑
Redis使用单Reactor单进程的模型
单Reactor多线程模型
相对于第一种模型来说,Reactor主线程在获取到IO的读写事件之后从channel中read数据,处理业务逻辑交由Worker线程池来处理(decode-compute-encode),handler收到Worker线程响应后通过send将响应结果返回给客户端。这样可以降低Reactor主线程的性能开销,从而更专注的做事件分发工作了,提升整个应用的吞吐。
Reactor单主线程虽然只承担所有事件的监听和响应,不承担业务逻辑处理,只要是单线程可能会存在性能问题
主从Reactor多线程模型
比起第二种模型,它是将Reactor分成两部分mainReactor和subReactor
mainReactor
负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给subReactor
从mainReactor线程池中随机选择一个Reactor线程作为acceptor线程,用于绑定监听端口,接收客户端连接
acceptor线程接收客户端连接请求之后创建新的SocketChannel,将其注册到mainReactor线程池的其它Reactor线程上,由其负责接入认证、IP黑白名单过滤、握手等操作
业务层的链路正式建立,将SocketChannel从主线程池的Reactor线程的多路复用器上摘除,重新注册到subReactor线程池的线程上
创建一个Handler用于下面处理其它读写事件
subReactor
主要做和建立起来的socket做数据交互和事件业务处理操作,通常subReactor个数上可与CPU个数等同
当有新的事件发生时,SubReactor会调用已创建的Handler进行响应
Handler通过read从channel读取数据后,会分发给后面的Worker线程池进行业务处理
Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果返回给Handler进行处理
Handler收到响应结果后通过Send将响应结果返回给Client
Nginx、Swoole、Memcached和Netty都是采用这种实现
Proactor
来了事件我来处理,处理完了我通知你
理论上proactor比Reactor效率要高一些,异步IO能够充分利用DMA特性,让IO操作与计算重叠,但是实现真正的异步IO,操作系统需要做大量的工作
目前Window下通过IOCP实现了真正的异步IO,而在Linux系统下的AIO并不完善,因此在Linux下实现高并发网络编程时都是以Reactor模式为主
多线程
无锁化的串行设计的
NioEventLoop读取到消息之后,直接调用 ChannelPipeline的 fireChannelRead( Object msg),只要用户不主动切换线程,一直会由 NioEventLoop调用到用户的 Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优
高效的并发编程
volatile的大量、正确使用
CAS和原子类的广泛使用
线程安全容器的使用
通过读写锁提升并发性能
高性能的序列化框架
Netty默认提供了对 Google Protobuf的支持,用户通过扩展 Netty的编解码接口,可以实现其他的高性能序列化框架,例如 Thrift
零拷贝
Netty的接收和发送 ByteBuffer采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket读写
实现 CompositeByteBuf,它对外将多个 ByteBuf封装成一个 ByteBuf
Netty文件传输类 DefaultFileRegion通过 transferTo方法将文件发送到目标 Channel中
可靠性
网络通信类故障
客户端连接超时
创建连接超时定时任务之后,会由NioEventLoop负责执行。如果已经连接超时,但是服务端仍然没有返回 TCP握手应答,则关闭连接。
设置完连接超时之后, Netty在发起连接的时候,会根据超时时间创建 ScheduledFuture挂载在 Reactor线程上,用于定时监测是否发生连接超时。
Netty的客户端连接超时参数与其他常用的 TCP参数一起配置,使用起来非常方便,上层用户不用关心底层的超时实现机制。这既满足了用户的个性化需求,又实现了故障的分层隔离。
通信对端强制关闭
连接网络闪断、对方进程突然宕机或者其他非正常关闭链路事件时,TCP链路就会发生异常。
由于 TCP是全双工的,通信双方都需要关闭和释放 Socket句柄才不会发生句柄的泄漏。
Netty底层已经自动对该故障进行了处理,及时关闭了句柄
链路正常关闭
对于短连接协议,例如HTTP协议,通信双方数据交互完成之后,通常按照双方的约定由服务端关闭连接,客户端获得TCP连接关闭请求之后,关闭自身的 Socket连接,双方正式断开连接
在实际的NIO编程过程中,经常存在一种误区:认为只要是对方关闭连接,就会发生 IO异常,捕获 IO异常之后再关闭连接即可。实际上,连接的合法关闭不会发生 IO异常,它是一种正常场景,如果遗漏了该场景的判断和处理就会导致连接句柄泄漏。
定制I/O故障
客户端的断连重连机制
消息的缓存重发
接口日志中详细记录故障细节
运维相关功能,例如告警、触发邮件/短信等
链路有效性检测
链路有效性检测需要周期性的心跳对链路进行有效性检测,一旦发生问题,可以及时关闭链路,重建TCP连接<br>
心跳检测机制
心跳检测的目的就是确认当前链路可用,对方活着并且能够正常接收和发送消息。做为高可靠的NIO框架, Netty也提供了心跳检测机制,下面我们一起熟悉下心跳的检测原理
三种层面的心跳
TCP层面的心跳检测,即 TCP的 Keep - Alive机制,它的作用域是整个 TCP协议栈
协议层的心跳检测,主要存在于长连接协议中。例如SMPP协议
应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现
Netty心跳机制
Netty的心跳检测实际上是利用了链路空闲检测机制实现
空闲检测机制
读空闲超时
链路持续时间t没有读取到任何消息
写空闲超时
链路持续时间t没有发送任何消息
读写空闲超时
链路持续时间t没有接收或者发送任何消息
两种心跳检测
Ping - Pong型心跳
由通信一方定时发送 Ping消息,对方接收到 Ping消息之后,立即返回 Pong应答消息给对方,属于请求 - 响应型心跳
Ping - Ping型心跳
不区分心跳请求和应答,由通信双方按照约定定时向对方发送心跳 Ping消息,它属于双向心跳
内存保护机制
链路总数的控制
每条链路都包含接收和发送缓冲区,链路个数太多容易导致内存溢出
单个缓冲区的上限控制
防止非法长度或者消息过大导致内存溢出
消息解码的时候,对消息长度进行判断,如果超过最大容量上限,则抛出解码异常,拒绝分配内存
缓冲区内存释放
防止因为缓冲区使用不当导致的内存泄露
为了防止因为用户遗漏导致内存泄漏,Netty在Pipe line的TailHandler中自动对内存进行释放
NIO消息发送队列的长度上限控制
Netty的 NIO消息发送队列 ChannelOutboundBuffer并没有容量上限控制,它会随着消息的积压自动扩展,直到达到 0x7fffffff
通过启动项的ChannelOption设置发送队列的长度,或者通过 - D启动参数配置该长度
流量整形
流量整形( Traffic Shaping)是一种主动调整流量输出速率的措施。一个典型应用是基于下游网络结点的 TP指标来控制本地流量的输出
流量整形对流量监管中需要丢弃的报文进行缓存——通常是将它们放入缓冲区或队列内,也称流量整形
流量整形原理是对每次读取到的ByteBuf可写字节数进行计算,获取当前的报文流量,然后与流量整形阈值对比。如果已经达到或者超过了阈值。则计算等待时间delay,将当前的ByteBuf放到定时任务Task中缓存,由定时任务线程池在延迟delay之后继续处理该ByteBuf
流量整形与流控的最大区别在于流控会拒绝消息,流量整形不拒绝和丢弃消息,无论接收量多大,它总能以近似恒定的速度下发消息,跟变压器的原理和功能类似
流量整形有两个作用
防止由于上下游网元性能不均衡导致下游网元被压垮,业务流程中断
防止由于通信模块接收消息过快,后端业务线程处理不及时导致的”撑死”问题
流量整形分类
全局流量整形
它的作用域针对所有的Channel
单链路流量整形
单链路流量整形与全局流量整形的最大区别就是它以单个链路为作用域,可以对不同的链路设置不同的整形策略
优雅停机接口
Java的优雅停机通常通过注册 JDK的 ShutdownHook来实现,当系统接收到退出指令后,首先标记系统处于退出状态,不再接收新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行
通常优雅退出有个时间限制,例如 30S,如果到达执行时间仍然没有完成退出前的操作,则由监控脚本直接 kill - 9 pid,强制退出
启动类
链式编程,类似于java8常见语法
ServerBootstrap
创建服务端的辅助启动类ServerBootstrap对象,目的是降低服务端的开发复杂度
Bootstrap
创建客户端辅助启动类Bootstrap对象
常用方法
group(parentGroup,childGroup)
参数配置Reactor线程模型
channel(Channel)
参数配置Channel类型,工厂模式创建Channel实例
服务端配置为NioServerSocketChannel,客户端配置为NioSocketChannel
option()
参数配置parentGroup的Option
handler(ChannelHandler)
参数配置parentGroup中accept线程的Handler
childOption(ChannelOption)
参数配置TCP属性
childAttr(AttributeKey)
参数配置childGroup的Reactor线程Attr
childHandler(ChildChannelHandler)
参数配置childGroup的Reactor线程Handler
bind(ip,port)
服务端监听端口,返回ChannelFuture
connect(ip,port)
客户端连接Tcp服务端,返回ChannelFuture
EventLoopGroup
通过适当的参数配置,就可以支持三种Reactor线程模型,类似于web应用中的tomcat
父类
EventLoopGroup->EventExcutorGroup->ScheduledExcutorService->ExcutorService
子类
NioEventLoopGroup
NioEventLoop并不是一个纯粹的I/O线程,它除了负责I/O的读写之外,还兼顾处理以下两类任务
系统Task
通过调用 NioEventLoop的 execute(Runnable task)方法实现
当I/O线程和用户线程同时操作网络资源时,为了防止并发操作导致的锁竞争,将用户线程的操作封装成Task放入消息队列中,由I/O线程负责执行,这样就实现了局部无锁化。
定时任务
通过调用 NioEventLoop的 schedule(Runnable command,long delay,TimeUnit unit)方法实现
线程模型
服务端启动的时候,创建了两个NioEventLoopGroup,它们实际是两个独立的Reactor线程池。线程池的隔离术,一个用于接收客户端的TCP连接,另一个用于处理I/O相关的读写操作,或者执行系统Task、定时任务Task等。
parentGroup
管理accept连接的线程
boss线程池
bossGroup对应的就是主线程池,只接收客户端的连接(注册,初始化逻辑)
将链路状态变更事件通知给ChannelPipeline
childGroup
管理Reactor事件的线程
worker线程池
具体的工作由workerGroup这个从线程池来完成
异步读取通信对端的数据报,发送读事件到ChannelPipeline
异步发送消息到通信对端,调用ChannelPipeline的消息发送接口
常用方法
shutdownGracefully()
优雅退出操作,通过该方法方便的关闭各种资源
channel
类似web应用的request&response
Netty提供了自己的Channel和其子类实现
NioServerSocketChannel
NioSocketChannel
常用方法
关联引用方法
pipeline()、Unsafe()
返回ChannelPipeline、Unsafe
closeFuture()
返回channel对象中的closeFuture对象
bind()、connect()、read()、write()、flush()、writeAndFlush()
与NIO中Channel对应的方法功能相似
入参可以新增ChannelPromise
返回参数ChannelFuture
config()
获取当前Channel的配置信息,例如CONNECT_ TIMEOUT_ MILLIS
attr()
设置用户自定义的属性,类似于request上的attribute
unsafe接口<br>
封装了java底层的socket操作,作为连接netty和java底层NIO的重要桥梁,channel会初始化unsafe来和socket打交道
ChannelPipeline
责任链模式,类似于web应用中所有Filter、Servlet行为管理类
它本质就是一个负责处理网络事件的职责链,网络事件以事件流的形式在ChannelPipeline中流转
ChannelPipeline的事件处理流程
用户自定义ChannelHandler会插入到head和tail之间
如果是ChannelInboundHandler的回调,根据插入的顺序从左向右进行链式调用,ChannelOutboundHandler则相反
常用方法
获取关联引用方法
channel()、context()
返回Channel、ChannelHandlerContext
触发outbound事件方法
ChannelPipeline拥有channel所有方法
触发Inbound事件方法
fireChannelActive()、fireChannelRead()、fireChannelRegistered()
返回ChannelPipeline
addLast、addFirst、addBefore、addAfter((EventExecutorGroup,Name,ChannelHandler))
ChannelHandlerContext
类似于web应用中单个Filter、Servlet的配置类ServletContext
它代表了ChannelPipeline和ChannelHandler之间的关联,创建于ChannelHandler被配置到ChannelPipeline的时候
每个ChannelHandler都对应一个ChannelHandlerContext
默认处理者
HeadContext
HeadContext继承AbstractChannelHandlerContext 实现了ChannelOutboundHandler, ChannelInboundHandler,所以其既可以处理inBound事件也可以处理outBound事件
TailContext
tail只是起了指引的作用,其具体处理业务逻辑都是由其父类实现,即调用下一个channelhandlerContext,其本身并没做什么实现
常用方法
关联引用方法
channel()、handler()、pipeline()、executor()
返回Channel、ChannelHandler、ChannelPipeline、EventExecutor
触发Inbound事件方法
fireChannelActive()、fireChannelRead()、fireChannelRegistered()
返回ChannelHandlerContext
触发outbound事件方法
ChannelHandlerContext拥有channel所有方法
attr()
ChannelHandlerContext的自定义属性
0 条评论
下一页