Netty优化-参数优化

Netty优化-参数优化

      • 1.1 参数调优
        • 1)CONNECT_TIMEOUT_MILLIS
        • 2)SO_BACKLOG
        • 3)ulimit -n
        • 4)TCP_NODELAY
        • 5)SO_SNDBUF & SO_RCVBUF
        • 6)ALLOCATOR
        • 7)RCVBUF_ALLOCATOR

1.1 参数调优

参数配置:

  • 服务端:

    • new ServerBootstrap().option() //配置ServerSocketChannel的参数
    • new ServerBootstrap().childOption() //配置SocketChannel的参数
  • 客户端:

    • new Bootstrap().option() //配置SocketChannel的参数
1)CONNECT_TIMEOUT_MILLIS
  • 属于 SocketChannal 参数

  • 用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出 timeout 异常

  • SO_TIMEOUT 主要用在阻塞 IO,阻塞 IO 中 accept,read 等都是无限等待的,如果不希望永远阻塞,使用它调整超时时间

@Slf4j
public class TestConnectionTimeout {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300)
                    .channel(NioSocketChannel.class)
                    .handler(new LoggingHandler());
            ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
            future.sync().channel().closeFuture().sync(); // 断点1
        } catch (Exception e) {
            e.printStackTrace();
            log.debug("timeout");
        } finally {
            group.shutdownGracefully();
        }
    }
}

另外源码部分 io.netty.channel.nio.AbstractNioChannel.AbstractNioUnsafe#connect

@Override
public final void connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    // ...
    // Schedule connect timeout.
    int connectTimeoutMillis = config().getConnectTimeoutMillis();
    if (connectTimeoutMillis > 0) {
        connectTimeoutFuture = eventLoop().schedule(new Runnable() {
            @Override
            public void run() {                
                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                ConnectTimeoutException cause =
                    new ConnectTimeoutException("connection timed out: " + remoteAddress); // 断点2
                if (connectPromise != null && connectPromise.tryFailure(cause)) {
                    close(voidPromise());
                }
            }
        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
    }
	// ...
}

实质是配置了超时时间后,eventLoop执行一个定时任务,如果超时时间到了后,还未连接上就执行定时任务中的方法,通过connectPromise.tryFailure(cause)将异常从nio线程传给主线程的future.sync(),然后被catch捕获

2)SO_BACKLOG
  • 属于 ServerSocketChannal 参数

Netty优化-参数优化_第1张图片

  1. 第一次握手,client 发送 SYN 到 server,状态修改为 SYN_SEND,server 收到,状态改变为 SYN_REVD,并将该请求放入 sync queue 队列
  2. 第二次握手,server 回复 SYN + ACK 给 client,client 收到,状态改变为 ESTABLISHED,并发送 ACK 给 server
  3. 第三次握手,server 收到 ACK,状态改变为 ESTABLISHED,将该请求从 sync queue 放入 accept queue

其中

  • 在 linux 2.2 之前,backlog 大小包括了两个队列的大小,在 2.2 之后,分别用下面两个参数来控制

  • sync queue - 半连接队列

    • 大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,在 syncookies 启用的情况下,逻辑上没有最大值限制,这个设置便被忽略
  • accept queue - 全连接队列

    • 其大小通过 /proc/sys/net/core/somaxconn 指定,在使用 listen 函数时,内核会根据传入的 backlog 参数与系统参数,取二者的较小值
    • 如果 accpet queue 队列满了,server 将发送一个拒绝连接的错误信息到 client

netty 中

可以通过 option(ChannelOption.SO_BACKLOG, 值) 来设置大小

调试:
关键断点为:io.netty.channel.nio.NioEventLoop#processSelectedKey每连一个客户端就放入 accept queue但是不取出来,然后超出配置的option(ChannelOption.SO_BACKLOG, 值),看效果

源码查看默认大小流程
backlog是在nio的bind方法中使用的,直接去ServerSocketChannel找bind方法,然后右键find uasges,然后找到 javaChannel().bind(localAddress, config.getBacklog());然后一层一层找到config就行了,最后找到这段代码,Windows默认200,linux默认128

SOMAXCONN = AccessController.doPrivileged(new PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
        // Determine the default somaxconn (server socket backlog) value of the platform.
        // The known defaults:
        // - Windows NT Server 4.0+: 200
        // - Linux and Mac OS X: 128
        int somaxconn = PlatformDependent.isWindows() ? 200 : 128;
        File file = new File("/proc/sys/net/core/somaxconn");
        BufferedReader in = null;
        try {
            // file.exists() may throw a SecurityException if a SecurityManager is used, so execute it in the
            // try / catch block.
            // See https://github.com/netty/netty/issues/4936
            if (file.exists()) {
                in = new BufferedReader(new FileReader(file));
                somaxconn = Integer.parseInt(in.readLine());
                if (logger.isDebugEnabled()) {
                    logger.debug("{}: {}", file, somaxconn);
                }
            } else {
                // Try to get from sysctl
                Integer tmp = null;
                if (SystemPropertyUtil.getBoolean("io.netty.net.somaxconn.trySysctl", false)) {
                    tmp = sysctlGetInt("kern.ipc.somaxconn");
                    if (tmp == null) {
                        tmp = sysctlGetInt("kern.ipc.soacceptqueue");
                        if (tmp != null) {
                            somaxconn = tmp;
                        }
                    } else {
                        somaxconn = tmp;
                    }
                }

                if (tmp == null) {
                    logger.debug("Failed to get SOMAXCONN from sysctl and file {}. Default: {}", file,
                                 somaxconn);
                }
            }
        } catch (Exception e) {
            logger.debug("Failed to get SOMAXCONN from sysctl and file {}. Default: {}", file, somaxconn, e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    // Ignored.
                }
            }
        }
        return somaxconn;
    }
});
3)ulimit -n

通常用于设置操作系统的文件描述符限制(File Descriptor Limit)或句柄数限制。这个命令通常用于限制一个进程可以同时打开的文件数或网络连接数。

ulimit -n 用于查看当前用户进程的文件描述符限制,这个限制控制了一个进程可以同时打开的文件数。在高并发的网络应用程序中,比如使用Netty编写的服务器,这个限制可能需要调整为较高的值,以便支持更多的并发连接。

要设置文件描述符限制,可以使用类似以下的命令:

ulimit -n 65536

这会将文件描述符限制设置为 65536,允许进程同时打开的文件数增加到这个数量,这对于需要处理大量并发连接的服务器应用程序非常有用。请注意,修改文件描述符限制可能需要超级用户权限。

4)TCP_NODELAY

TCP_NODELAY 是一个TCP socket选项,通常用于控制TCP数据传输的延迟和性能。

在TCP通信中,数据通常被缓冲并等待一段时间,以便将多个小数据包合并成一个更大的数据包,以减少网络开销。这种缓冲的行为可以提高网络的利用率,但也会引入一些延迟,因为数据需要等待其他数据以形成更大的数据包。

通过启用TCP_NODELAY选项,可以禁用这种缓冲,从而允许小数据包立即传输。这对某些应用程序非常重要,特别是需要低延迟的应用程序,如实时音视频通信、在线游戏等。当TCP_NODELAY被启用时,数据将立即发送,而不需要等待缓冲区中的其他数据。

netty默认关闭,属于服务端SocketChannal 参数,使用new ServerBootstrap().childOption(ChannelOption.TCP_NODELAY, true);开启

5)SO_SNDBUF & SO_RCVBUF

发送缓冲区,接收缓冲区,系统自动调整

  • SO_SNDBUF 属于 SocketChannal 参数
  • SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)
6)ALLOCATOR
  • 属于 SocketChannal 参数

Allocator的中文解释:

内存分配:Allocator负责从堆内存或直接内存(off-heap)中分配内存块,以便存储数据。这些内存块通常被分成较小的块,每个块可以用来创建一个ByteBuf。

内存管理:Allocator跟踪已分配的内存块,并负责在不再需要这些内存块时将其释放,以便回收内存。这有助于防止内存泄漏和确保内存的高效利用。

内存池:Allocator通常使用内存池来重复使用内存块。这意味着当一个ByteBuf不再需要时,它的内存块不会立即释放,而是放回内存池以供将来重用。这可以提高性能,减少内存分配和释放的开销。

分配策略:Allocator可以采用不同的分配策略,例如池化(Pooling)和非池化(Non-pooling)。池化分配策略使用内存池来重复使用内存块,而非池化策略每次都会分配新的内存块。

总之,Allocator是Netty中用于管理内存分配和释放的关键组件,它有助于优化网络应用程序的性能,确保内存的高效使用,并降低内存管理的复杂性。

想配置池化非池化,直接内存堆内存,需要找到默认配置项,在ChannelConfig中配置的

7)RCVBUF_ALLOCATOR
  • 属于 SocketChannal 参数
  • 控制 netty 接收缓冲区大小
  • 负责入站数据的分配,决定入站缓冲区的大小(并可动态调整),统一采用 direct 直接内存,具体池化还是非池化由 allocator 决定
  • 和ALLOCATOR共同协作完成对bytebuf的分配

Netty优化-参数优化_第2张图片

你可能感兴趣的:(netty,netty,allocator,backlog,ulimit,TCP_NODELAY)