• 【Netty 的优化和参数】


    一、优化

    1、Java, Json 序列化

    可以看看 项目 https://github.com/qingguox/Boot-Practice/blob/feature_20220527_study_netty/boot-practice-component/src/main/java/com/xlg/component/netty/chat/protocol/Serializer.java

    2、一些参数

    CONNECT_TIMEOUT_MILLIS

    • 属于 SocketChannal 的参数
    • 用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出 timeout 异常
    • 注意:Netty 中不要用成了SO_TIMEOUT 主要用在阻塞 IO,而 Netty 是非阻塞 IO
    public class TestParam {
        public static void main(String[] args) {
            // SocketChannel 5s内未建立连接就抛出异常
            new Bootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
            
            // ServerSocketChannel 5s内未建立连接就抛出异常
            new ServerBootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000);
            // SocketChannel 5s内未建立连接就抛出异常
            new ServerBootstrap().childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 客户端通过 Bootstrap.option 函数来配置参数,配置参数作用于 SocketChannel
    • 服务器通过 ServerBootstrap来配置参数,但是对于不同的 Channel 需要选择不同的方法
      • 通过 option 来配置 ServerSocketChannel 上的参数
      • 通过 childOption 来配置 SocketChannel 上的参数

    源码分析

    客户端中连接服务器的线程是 NIO 线程,抛出异常的是主线程。这是如何做到超时判断以及线程通信的呢
    AbstractNioChannel.AbstractNioUnsafe.connect方法中

    public final void connect(
                    final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
        
        ...
            
        // Schedule connect timeout.
        // 设置超时时间,通过option方法传入的CONNECT_TIMEOUT_MILLIS参数进行设置
        int connectTimeoutMillis = config().getConnectTimeoutMillis();
        // 如果超时时间大于0
        if (connectTimeoutMillis > 0) {
            // 创建一个定时任务,延时connectTimeoutMillis(设置的超时时间时间)后执行
            // schedule(Runnable command, long delay, TimeUnit unit)
            connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                @Override
                public void run() {
                    // 判断是否建立连接,Promise进行NIO线程与主线程之间的通信
                    // 如果超时,则通过tryFailure方法将异常放入Promise中
                    // 在主线程中抛出
                    ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                    ConnectTimeoutException cause = new ConnectTimeoutException("connection timed out: " + remoteAddress);
                    if (connectPromise != null && connectPromise.tryFailure(cause)) {
                        close(voidPromise());
                    }
                }
            }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
        }
        
       	...
            
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    超时的判断主要是通过 Eventloop 的 schedule 方法和 Promise 共同实现的

    • schedule 设置了一个定时任务,延迟connectTimeoutMillis秒后执行该方法
    • 如果指定时间内没有建立连接,则会执行其中的任务
      • 任务负责创建 ConnectTimeoutException 异常,并将异常通过 Pormise 传给主线程并抛出

    SO_BACKLOG

    该参数是 ServerSocketChannel 的参数

    三次握手与连接队列

    第一次握手时,因为客户端与服务器之间的连接还未完全建立,连接会被放入半连接队列
    [image.png
    当完成三次握手以后,连接会被放入全连接队列中
    image.png
    在 linux 2.2 之前,backlog 大小包括了两个队列的大小,在 linux 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

    image.png

    作用
    在Netty中,SO_BACKLOG主要用于设置全连接队列的大小。当处理Accept的速率小于连接建立的速率时,全连接队列中堆积的连接数大于SO_BACKLOG设置的值时,便会抛出异常

    全连接队列 其实就是server accept 阻塞处理的client个数,如果client太多,会先让在全连接队列中等待.

    设置方式如下

    // 设置全连接队列,大小为2
    new ServerBootstrap().option(ChannelOption.SO_BACKLOG, 2);
    
    • 1
    • 2

    默认值

    backlog参数在NioSocketChannel.doBind方法被使用

    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其中backlog被保存在了DefaultServerSocketChannelConfig配置类中

    private volatile int backlog = NetUtil.SOMAXCONN;
    
    • 1

    具体的赋值操作如下

    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设置为Linux配置文件中设置的值
                    somaxconn = Integer.parseInt(in.readLine());
                    if (logger.isDebugEnabled()) {
                        logger.debug("{}: {}", file, somaxconn);
                    }
                } else {
                    ...
                }
                ...
            }  
            // 返回backlog的值
            return somaxconn;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • backlog的值会根据操作系统的不同,来选择不同的默认值
      • Windows 200
      • Linux/Mac OS 128
    • 如果配置文件/proc/sys/net/core/somaxconn存在,会读取配置文件中的值,并将backlog的值设置为配置文件中指定的

    TCP_NODELAY

    • 属于 SocketChannal 参数
    • 因为 Nagle 算法,数据包会堆积到一定的数量后一起发送,这就可能导致数据的发送存在一定的延时
    • 该参数默认为false,如果不希望的发送被延时,则需要将该值设置为true

    SO_SNDBUF & SO_RCVBUF

    • SO_SNDBUF 属于 SocketChannal 参数
    • SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)
    • 该参数用于指定接收方与发送方的滑动窗口大小

    ALLOCATOR

    • 属于 SocketChannal 参数
    • 用来配置 ByteBuf 是池化还是非池化,是直接内存还是堆内存

    使用

    // 选择ALLOCATOR参数,设置SocketChannel中分配的ByteBuf类型
    // 第二个参数需要传入一个ByteBufAllocator,用于指定生成的 ByteBuf 的类型
    new ServerBootstrap().childOption(ChannelOption.ALLOCATOR, new PooledByteBufAllocator());
    
    • 1
    • 2
    • 3

    ByteBufAllocator类型

    // true表示使用直接内存
    new PooledByteBufAllocator(true);
    // false表示使用堆内存
    new PooledByteBufAllocator(false);
    // 非池化
    // ture表示使用直接内存
    new UnpooledByteBufAllocator(true);
    // false表示使用堆内存
    new UnpooledByteBufAllocator(false);
    
    有系统参数控制,  默认堆内存   一般io都是直接内存
    有参数数控制,   如果是安卓,是非池化,其他是池化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    RCVBUF_ALLOCATOR

    • 属于 SocketChannal 参数
    • 控制 Netty 接收缓冲区大小
    • 负责入站数据的分配,决定入站缓冲区的大小(并可动态调整),统一采用 direct 直接内存,具体池化还是非池化由 allocator 决定

    image.png

    二、自制rpc

    可以看看这个.
    https://github.com/qingguox/Boot-Practice/tree/feature_20220527_study_netty/boot-practice-component/src/main/java/com/xlg/component/netty/rpc

  • 相关阅读:
    如何解决swagger-editor在线接口调试时的跨域问题
    STL算法——常用排序算法(sort、random_suffle、merge、reverse)
    烧写系统镜像
    RK3576:革新智能设备体验的高性能AI芯片
    图——邻接表
    【Python入门】第一章:Python介绍
    Spring Data JDBC - 如何对聚合根进行部分更新?
    第十三届蓝桥杯大赛湖南中医药大学第1场选拔赛总结(java版)
    数据结构之二叉树(前提知识)
    pytorch学习——LSTM和GRU
  • 原文地址:https://blog.csdn.net/qq_41773026/article/details/125627845