第五章: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