调优参数
调整System参数夯实基础
-
Liunx系统参数
例如
/proc/sys/net/ipv4/tcp_keepalive_time
-
Netty支持的系统参数:
都是以SO开头的
例如:
serverBootStrap.option(ChannelOption.SO_BACKLOG, 1024);
不同的socketChannel通过不同的option设置
- socketChannel -> .childOption
- serverSocketChanel -> .option
Linux系统参数
-
进行TCP连接时,系统为每个TCP连接创建一个socket句柄,也就是一个文件句柄。但是Linux对每个进程打开的文件句柄数量做了限制,如果超出,会报错:
Too many open file
ulimit -n [xxx]
注意:ulimit命令修改的数值
只对当前登录用户
的目前使用环境有效,系统重启或者用户退出后机会失效,所以可以作为程序启动脚本一部分,让它在程序启动前执行
Netty支持的系统参数(ChannelOption.[xxx])讨论:
-
不考虑UDP:
- IP_MULTICAST_TTL
-
不考虑OIO编程:
-
ChannelOption<Integer> SO_TIMEOUT = (“SO_TIMEOUT”);
OIO是阻塞编程,这个参数是控制阻塞时间的
-
SocketChannel(7个参数,通过childOption设置)
Netty系统相关参数 | 功能 | 默认值 |
---|---|---|
SO_SNDBUF | TCP数据发送缓冲区大小(调整时用带宽乘以RTT值,高版本linux内核会动态调整) | /proc/sys/net/ipv4/tep_wmem:4k [min,default,max]动态调整 |
SO_RCVBUF | TCP数据接收缓冲区大小 | /proc/sys/net/ipv4/rmem:4k |
SO_KEEPALIVE | TCP层keepalive | 默认关闭 |
SO_REUSEADDR | 地址重用,解决"Address already in use" 常用开启场景:多网卡(IP)绑定相同端口;让关闭连接释放的端口更早可使用 |
默认不开启 澄清:不是让TCP绑定完全相同IP+Port来重复启动 |
SO_LINGER | 关闭socket的延迟时间,默认禁用该功能,socket.close()方法立即返回 | 默认不开启 |
IP_TOS | 设置IP头部的Type-of-Service字段,用于描述IP包的优先级和Qos选项。例如倾向于延时还是吞吐量? | 1000 - minimize delay 0100 - maximize throughput 0010 - maximize reliability 0001 - minimize monetary const 0000 - normal service(默认值) The value of the socket option is a hint. An implementation may ignore the value,or ignore specific values. |
TCP_NODELAY | 设置是否启用Nagle算法:用将小的碎片数据连接成更大的报文来提高发送效率 | False 如果需要发送一些较小的报文,则需要禁用该算法 |
ServerSocketChannel(3个参数,通过option设置)
Netty系统相关参数 | 功能 | 备注 |
---|---|---|
SO_RCVBUF | 为Accept创建的socket channel设置 SO_RCVBUF: “Sets a default proposed value for the SO_RVCBUF option for sockets accepted from this ServerSocket” |
创建好SockerChannel之后就可以立马接收数据了,如果把SockerChannel设置SO_RCVBUF,用之前的方式就比较迟了,可能已经接收到数据了,所以在创建的时候设置,就可以保证创建之后立马就生效 |
SO_REUSEADDR | 是否可重用端口 | 默认false |
SO_BACKLOG | 最大的等待连接数量 | Netty在Linux下值的获取(io.netty.util.NetUtil): 先尝试:/proc/sys/net/core/somaxcon 然后尝试:sysctl 最终没有获取到,用默认值:128 使用方式: javaChannel().bind(localAddress,config.getBackLog()); |
权衡Netty核心参数
-
参数调增要点:
- option/childOption分不清:不会报错,但是不会生效
- 不懂不要动,避免过早优化
- 可配置(动态配置更好)
-
需要调整的参数:
-
最大打开文件数
-
TCP_NODELAY设置为true SO_BACKLOG可以调整大点,如1024 SO_REUSEADDR(酌情处理)
-
serverBootstrap.childOption(NioChannelOption.TCP_NODELAY,true); serverBootstrap.childOption(NioChannelOption.SO_BACKLOG,1024);
-
非系统参数
- ChannelOption
- childOption(ChannelOption.[xxx], [yyy])
- option(ChannelOption.[xxx], [yyy])
- System property
- -Dio.netty.[xxx]=[yyy]
ChannelOption(非系统相关,共11个)
Netty参数 | 功能 | 默认值 |
---|---|---|
WRITE_BUFFER_WATER_MARK | 高低水位线、间接防止写数据OOM | 32k到64k,分别对应低水位线和高水位线。 为什么不大? 这个参数是channel级别的,也就是说,一个连接就有一个这个设置 |
CONNECT_TIMEOUT_MILLS | 客户端 连接服务器最大允许时间 |
30秒 |
MAX_MESSAGE_PER_READ | 最大允许”连续“读次数 | 16次 |
WRITE_SPIN_COUNT | 最大允许”连续“写次数 | 16次 |
ALLOCATOR | ByteBuf分配器 | ByteBufAllocatior.DEFAULT:大多池化、堆外 |
RCVBUF_ALLOCATOR | 数据接收ByteBuf分配大小计算器+读次数控制器 | AdaptiveRcvByteBufAllocator |
AUTO_READ | 是否监听”读事件“ | 默认:监听”读“事件: 设置此标记的方法也触发注册或移除读事件的监听 |
AUTO_CLOSE | ”写事件“失败,是否关闭连接 | 默认打开,因为不关闭,下次还写,可能还是失败 |
MESSAGE_SIZE_ESTIMATOR | 数据(ByteBuf、FileRegin等)大小计算器 | DefaultMessageSizeEstimator.DEFAULT 例如计算ByteBuf:byteBuf.ReadableBytes() |
SINGLE_EVENTEXECUTOR_PER_GROUP | 当增加一个handler并且指定EventExecutorGroup时:决定这个handler是否只用EventExecutorGroup中的一个固定的EventExecutor(取决于next()实现) | 默认true 这个handler不管是否共享,绑定上唯一一个eventExecutor,所以小名 pinEventExecutor 没有指定EventExecutorGroup,复用channel的NioEventLoop |
ALLOW_HALF_CLOSURE | 关闭连接时,允许边关 https://issues.jboss.org/browse/NETTY-236 |
默认不允许半关,如果允许,处理变成: shutdownInput(); pipeline.fireUserEventTriggered(channelInputShutdownEvent.INSTANCE); |
ALLOCATOR与RCVBUF_ALLOCATOR
-
功能关联:
ALLOCATOR负责ByteBuf怎么分配(例如:从哪里分配),RCVBUF_ALLOCATOR负责计算为了接收数据分配多少ByteBuf
RCVBUF_ALLOCATOR有两种实现,一种是固定size的,一种是自适应size的
例如,AdaptiveRecvByteBufAllocator有两大功能:
- 动态计算下一次分配bytebuf的大小:guess();
- 判断是否可以继续读:continueReading()
-
代码关联:
io.netty.channel.AdaptiveRecvByteBufAllocator.HandleImpl handle = AdaptiveRecvByteBufAllocator.newHandler();
ByteBuf bytebuf = handle.allocate(ByteBufAllocator)
其中 allocate的实现:
ByteBuf allocate(ByteBufAllocator alloc){
return alloc.ioBuffer(guess());
}
System property(-Dio.netty.xxx, 50+)
- 多种实现的切换:-Dio.netty.noJdkZlibDecoder 切换ZlibDecoder的实现
- 参数的调优:-Dio.netty.eventLoopThreads
- 功能的开启关闭:-Dio.netty.noKeySetOptimization
Netty参数 | 功能 | 备注 |
---|---|---|
io.netty.eventLoopThreads | IO Thread数量 | 默认availableProcessors*2 |
io.netty.availableProcessors | 指定availableProcessors | 参考docker/VM等情况,jdk10以下,获取到的是宿主机上的资源,而不是docker容器的资源 |
io.netty.allocator.type | unpooled/pooled | 池化还是非池化 |
io.netty.noPreferDirect | true/false | 堆内还是堆外 |
io.netty.noUnsafe | true/false | 是否使用sun.misc.Unsafe |
io.netty.leakDetection.level | DISABLED/SIMPLE等 | 内存泄露检测级别,默认SIMPLE |
io.netty.native.workdir io.netty.tmpdir |
临时目录 | 从jar中解出native库存放的临时目录 |
io.netty.processId io.netty.machineld |
进程号 机器硬件地址 |
计算channel的ID: MACHINE_ID+PROCESS_ID+SEQUENCE+TIMESTAMP+RANDOM |
io.netty.eventLoop.maxPendingTasks io.netty.eventExecutor.maxPendingTasks |
存的task最大数目 | 默认Integer.MAX_VALUE,显示设置为准,不低于16 |
io.netty.handler.ssl.noOpenSsl | 关闭open ssl使用 | 优选open ssl |
补充说明
-
一些其他重要的参数
-
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
workerGroup.setIoRatio(50);
IoRatio:NioEventLoop是处理IO事件的,很多时候会定义自己的handler去处理自己的业务,如果没有指定自己的执行器,就会和NioEventLoop复用,这样就会涉及到比例的问题,到底是IO处理的时间多还是业务处理的时间多
-
-
注意参数的关联
- 临时存放native库的目录:->io.netty.native.workdir优先级大于io.netty.tmpdir
-
注意参数的变更
io.netty.noResourceLeakDetection-> io.netty.leakDetection.level
比较费解的三个参数
- SO_REUSEADDR
- SO_LINGER
- ALLOW_HALF_CLOSURE
SO_REUSEADDR
- 关闭连接释放的端口更早可使用
主动关闭的一方,会等待对方的通道关闭,也就是服务端发了FIN包,客户端回复了一个ACK进行确认,然后客户端会进入TIME_WAIT状态,客户端发送了ACK,但是对端不一定能接受到这个ACK,如果中途丢失了这个ACK,那么这个FIN包会重新发送,如果不等TIME-WAIT的时间,就会返回一个RST的错误。
-
端口可用等待时间:
TCP是一个可靠的传输,但是实际传输中,可能会出现遗漏。
TimeWait:2*MSL(Maximum segment lifetime 包的最大生命周期)
如果不进行TIME-WAIT的话,直接进如了CLOSED状态,这个端口立马可以重用了,这个时候,中途遗漏的包就会到达新的程序
Liunx中TimeWait一般为60s,可以通过SO_REUSEADDR可以将这个时间降到协议认为的安全时间(1秒)
- 协议认为的安全:比如已经发完了最后的ACK,这个时候,另外一个链接想复用当前的端口,如果我们发送,从进入TimeWait到想复用端口之间,已经有1秒了,这个时间就可以把这个端口给新的应用使用了
-
如果网络环境比较好,并且比较自信不会遗漏包什么的,就可以将这个值减少
SO_LINGER
左图:当发送完数据后,close掉这个连接,close就是发送FIN包,发送之后可能就直接close return了,后面的底层会等待FIN包的ACK以及前面数据的ACK,然后等待对方法FIN包来关闭通道数据,最终会ACK data以及FIN包。close return掉之后,后面的可靠性保证都是异步的过程,close完之后,可能进程就直接关闭掉了,后面的步骤可能就无法走到了,所以这不是一种非常优美的关闭。
右图:设置SO_LINGER参数,稍微逗留一会,逗留到至少data已经FIN包已经回来了,这个时候再让close return 走,这样相比左边的操作就安全很多
这个设置为了提高可靠性,但是实际中一般不会开启这个参数,我们希望使用NIO的非阻塞功能,当然也包括关闭,一般情况下也不会出现close之后,data和FIN回不来的情况,一般调用完close之后,不会将程序结束掉,所以后面的仍然会继续往下走,而且在网络环境比较好的情况下都能够走完
ALLOW_HALF_CLOSURE 半关
TCP的连接是一个双向通道,所以关闭连接就是一个关闭双向通道的过程,正常情况下,channel.close(),对端收到一个EOF(-1),然后server返回一个FIN的ACK,server调用channel.close()发送一个FIN,这样就完成双向通道的关闭。
有的时候,只想关闭其中一个通道,这个时候就要调用channe.shutdownOutout(),这样就会给对端发送一个FIN包,对端读到EOF(-1),以前不支持半关时,对端会直接调用channel.close()关闭掉连接,现在支持了半关,就会调用channel.shutdonwInput(),server仍然可以发送数据给client。
client端channel.shutdownOutput()之后,client就不能再发送数据了,如果再发送,就会抛出一个Exception说这个channel已经关闭掉了。
server端调用channel.shutdownInput()之后就不能再读取数据了,读取到的依旧是EOF(-1)。