Netty总结(三)

第五章:Netty实战进阶,把“玩具”变成产品

调优参数:调整System参数

  • Linux 系统参数

例如:/proc/sys/net/ipv4/tcp_keepalive_time

  • 进行 TCP 连接时,系统为每个 TCP 连接创建一个 socket 句柄,也就是一个文件句柄,但是 Linux对每个进程打开的文件句柄数量做了限制,如果超出:报错 “Too many open file”。

ulimit -n [xxx]

注意:ulimit 命令修改的数值只对当前登录用户的目前使用环境有效,系统重启或者用户退出后就会失效,所以可以作为程序启动脚本一部分,让它程序启动前执行。

  • Netty 支持的系统参数(ChannelOption.[XXX] )

例如:serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);

  • 不考虑 UDP :

  • IP_MULTICAST_TTL

  • 不考虑 OIO 编程:

    • ChannelOption<Integer> SO_TIMEOUT = ("SO_TIMEOUT");

Netty

参数

SocketChannel-> .childOption

 

ServerSocketChannel -> .option

 

调优参数:权衡 Netty 核心参数

  • 参数调整要点:

    • option/childOption 傻傻分不清:不会报错,但是会不生效;

    • 不懂不要动,避免过早优化。

    • 可配置(动态配置更好)

  • 需要调整的参数:

    • 最大打开文件数

    • TCP_NODELAY SO_BACKLOG SO_REUSEADDR(酌情处理)

  • ChannelOption

    • childOption(ChannelOption.[XXX], [YYY])

    • option(ChannelOption.[XXX], [YYY])

  • System property

    • -Dio.netty.[XXX] = [YYY]

Netty参数

参数

备注

ChannelOption

(非系统相关:共11个)

 

  • ALLOCATOR 与 RCVBUF_ALLOCATOR

    • 功能关联:

ALLOCATOR 负责 ByteBuf 怎么分配(例如:从哪里分配),

RCVBUF_ALLOCATOR 负责计算为接收数据接分配多少 ByteBuf:

例如,AdaptiveRecvByteBufAllocator 有两大功能:

(1)动态计算下一次分配 bytebuf 的大小:guess();

(2)判断是否可以继续读:continueReading()

  • 代码关联:

io.netty.channel.AdaptiveRecvByteBufAllocator.HandleImpl handle =

AdaptiveRecvByteBufAllocator.newHandle();

ByteBuf byteBuf = handle.allocate(ByteBufAllocator)

其中: allocate的实现:

ByteBuf allocate(ByteBufAllocator alloc){return alloc.ioBuffer(guess());}

System property

(-Dio.netty.xxx)

 

  • 多种实现的切换:-Dio.netty.noJdkZlibDecoder

  • 参数的调优: -Dio.netty.eventLoopThreads

  • 功能的开启关闭: -Dio.netty.noKeySetOptimization

图解三个费脑的参数

参数

图解

SO_REUSEADDR

 

SO_LINGER

 

ALLOW_HALF_CLOSURE

 

跟踪诊断:如何让应用易诊断

做法

备注

完善“线程名” 

 

完善“Handler”名称

 

使用好Netty日志

  • Netty 日志的原理及使用

    • Netty 日志框架原理

    • 修改 JDK logger 级别

    • 使用 slf4j + log4j 示例

    • 衡量好 logging handler 的位置和级别

跟踪诊断:可视化

Netty值得可视化的数据

数据

外在

 

内在

 

跟踪诊断:防止内存泄漏

Netty 内存泄漏是指什么?

  • 原因:“忘记”release

ByteBuf buffer = ctx.alloc().buffer();

buffer.release() / ReferenceCountUtil.release(buffer)

  • 后果:资源未释放 -> OOM

    • 堆外:未 free(PlatformDependent.freeDirectBuffer(buffer));

    • 池化:未归还 (recyclerHandle.recycle(this))

Netty 内存泄漏检测核心思路

引用计数(buffer.refCnt())

  • 判断历史人物到底功大于过,还是过大于功?

功 +1, 过 -1, = 0 时:尘归尘,土归土,资源也该释放了

  • 那什么时候判断?“盖棺定论”时 -> 对象被 GC 后

弱引用(Weak reference)

  • 强引用与弱引用

    • String 我是战斗英雄型强保镖 = new String(我是主人));

    • WeakReference<String> 我是爱写作的弱保镖 = new WeakReference<String>(new String(我是主人));               

    •  只有一个爱写作的保镖(弱引用)守护(引用)时:刺客(GC)来袭,主人(referent)必挂(GC掉)。不过主人挂掉的(被 GC 掉)时候,我还是可以发挥写作特长:把我自己记到“小本本”上去。

  • WeakReference<String> 我是爱写作的弱保镖 = new WeakReference<String>(new String(我是主人),我的小本本ReferenceQueue);

 

Netty 内存泄漏检测的源码解析

  • 全样本?抽样?: PlatformDependent.threadLocalRandom().nextInt(samplingInterval)

  • 记录访问信息:new Record() : record extends Throwable

  • 级别/开关:io.netty.util.ResourceLeakDetector.Level

  • 信息呈现:logger.error

  • 触发汇报时机: AbstractByteBufAllocator#buffer() :io.netty.util.ResourceLeakDetector#track0

用 Netty 内存泄漏检测工具做检测

  • 方法:-Dio.netty.leakDetection.level=PARANOID

  • 注意:

    • 默认级别 SIMPLE,不是每次都检测

    • GC 后,才有可能检测到

    • 注意日志级别

    • 上线前用最高级别,上线后用默认

优化使用:用好注解

@Sharable

标识 handler 提醒可共享,不标记共享的不能重复加入 pipeline

@Skip

跳过 handler 的执行

@UnstableApi

提醒不稳定,慎用

@SuppressJava6Requirement

 

@SuppressForbidden

 

优化使用:整改线程模型

CPU密集型

  • 保持当前线程模型:

    • Runtime.getRuntime().availableProcessors() * 2

    • io.netty.availableProcessors * 2

    • io.netty.eventLoopThreads

IO密集型

  • 整改线程模型:独立出 “线程池”来处理业务

    • 在 handler 内部使用 JDK Executors

    • 添加 handler 时,指定1个:

      • EventExecutorGroup eventExecutorGroup = new UnorderedThreadPoolEventExecutor(10);

      • pipeline.addLast(eventExecutorGroup, serverHandler)

优化使用:增强写、延迟与吞吐量的关系

 全部“加急式”快递

 

利用channelReadComplete

 

  • 缺点:

    • 不适合异步业务线程 (不复用 NIO event loop) 处理:

      • channelRead 中的业务处理结果的 write 很可能发生在 channelReadComplete 之后

    • 不适合更精细的控制:例如连读 16 次时,第 16 次是 flush,但是如果保持连续的次数不变,如何做到 3 次就 flush?

flushConsolidationHandler

 

优化使用:流量整形

流量整形的用途

有意 --> 网盘限速 无奈 --> 景点限流

Netty 内置的三种流量整形

Netty 流量整形源码分析与总结

  • 读写流控判断:按一定时间段 checkInterval (1s) 来统计。writeLimit/readLimit 设置的值为0时,表示关闭写整形/读整形

  • 等待时间范围控制:10ms (MINIMAL_WAIT) -> 15s (maxTime)

  • 读流控:取消读事件监听,让读缓存区满,然后对端写缓存区满,然后对端写不进去,对端对数据进行丢弃或减缓发送。

  • 写流控:待发数据入 Queue。等待超过 4s (maxWriteDelay) || 单个 channel 缓存的数据超过4M(maxWriteSize) || 所有缓存数据超过400M (maxGlobalWriteSize)时修改写状态为不可写。

安全增强:高低水位线

Netty OOM 的根本原因

  • 根源:进(读速度)大于出(写速度)

  • 表象:

    • 上游发送太快:任务重

    • 自己:处理慢/不发或发的慢:处理能力有限,流量控制等原因

    • 网速:卡

    • 下游处理速度慢:导致不及时读取接受 Buffer 数据,然后反馈到这边,发送速度降速

ChannelOutboundBuffer

  • 存的对象:Linked list 存 ChannelOutboundBuffer.Entry

  • 解决方式:判断 totalPendingSize > writeBufferWaterMark.high() 设置unwritable

    • ChannelOutboundBuffer

 

TrafficShapingHandler

  • 存的对象:messagesQueue 存 ChannelTrafficShapingHandler.ToSend

  • 解决方式:判断 queueSize > maxWriteSize 或 delay > maxWriteDelay 设置 unwritable

    • AbstractTrafficShapingHandler:

 

unwritable

 

Netty OOM – 对策

安全增强:启用空闲检测

  • 服务器加上 read idle check – 服务器 10s 接受不到 channel 的请求就断掉连接

    • 保护自己、瘦身(及时清理空闲的连接)

  • 客户端加上 write idle check + keepalive – 客户端 5s 不发送数据就发一个 keepalive

    • 避免连接被断

    • 启用不频繁的 keepalive

安全增强:黑白名单

什么是“cidrPrefix”

Netty 地址过滤功能分析

  • 同一个 IP 只能有一个连接

  • IP 地址过滤:黑名单、白名单

安全增强:SSL

什么是SSL

  • SSL/TLS 协议在传输层之上封装了应用层数据,不需要修改应用层协议的前提下提供安全保障

  • TLS(传输层安全)是更为安全的升级版 SSL