第五章: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 负责 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) | ![]()
|
|
图解三个费脑的参数
参数 | 图解 |
---|---|
SO_REUSEADDR | ![]()
|
SO_LINGER | ![]()
|
ALLOW_HALF_CLOSURE | ![]()
|
跟踪诊断:如何让应用易诊断
做法 | 备注 |
---|---|
完善“线程名” | ![]()
|
完善“Handler”名称 | ![]()
|
使用好Netty日志 |
|
跟踪诊断:可视化
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 时:尘归尘,土归土,资源也该释放了
|
弱引用(Weak reference) |
|
|
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密集型 |
|
IO密集型 |
|
优化使用:增强写、延迟与吞吐量的关系
全部“加急式”快递 | ![]()
|
利用channelReadComplete |
|
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 |
|
TrafficShapingHandler |
|
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