Netty(4)参数调优

young 1,896 2022-05-22

调优参数

调整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());
IP_TOS

权衡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有两大功能:

    1. 动态计算下一次分配bytebuf的大小:guess();
    2. 判断是否可以继续读: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

  • 关闭连接释放的端口更早可使用

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

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 半关

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)。