07、Netty学习笔记—(聊天业务优化:参数调优)

举报
长路 发表于 2022/11/28 08:28:27 2022/11/28
【摘要】 文章目录Option配置参数①CONNECT_TIMEOUT_MILLIS(连接超时设定)参数说明+代码示例源码分析②SO_BACKLOG(全连接队列中存储连接个数)参数说明+代码示例源码分析backlog其他参数③tcp_nodelay(一次尽可能发出数据,而非缓冲区缓存;禁用nagle算法)④so_sendbuf & so_rcvbuf(发送缓冲区和接收缓冲区)⑤allocator(配

@[toc]

netty笔记汇总:Netty学习指南(资料、文章汇总)

根据黑马程序员netty视频教程学习所做笔记,部分内容图例来源黑马笔记

笔记demo案例仓库地址: Github-【netty-learn】Gitee-【netty-learn】

Option配置参数

new ServerBootstrap().option()  //是给ServerSocketChannel配置参数
new ServerBootstrap().childOption()  //是给SocketChannel配置参数

①CONNECT_TIMEOUT_MILLIS(连接超时设定)

参数说明+代码示例

参数说明

来源:属于 SocketChannal 参数。

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

注意:对于SO_TIMEOUT参数主要用在阻塞 IO,阻塞 IO 中 accept,read 等都是无限等待的,如果不希望永远阻塞,使用它调整超时时间。(一句话控制阻塞的超时时间,到达一定时间直接停止阻塞向下执行)

代码示例

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName Client
 * @Author ChangLu
 * @Date 2022/1/15 17:33
 * @Description ChannelOption.CONNECT_TIMEOUT_MILLIS:连接超时参数设置,到达一定时间依旧没有连接就会抛出超时异常。
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1)
                    .channel(NioSocketChannel.class)
                    .handler(new LoggingHandler());
            ChannelFuture future = bootstrap.connect("localhost", 8081);
            future.sync().channel().closeFuture().sync(); // 断点1
        } catch (Exception e) {
            e.printStackTrace();
            log.debug("timeout");
        } finally {
            group.shutdownGracefully();
        }
    }
}

运行结果:若是在进行连接过程中指定时间内没有返回响应,那么就会抛出一个连接超时异常;相反若是服务器没有启动,客户端来尝试连接,就会出现服务被拒绝的异常(其与设置连接超时时间无关)

image-20220115193426890

image-20220115193602694


源码分析

定位:可通过报错异常指定的类。

连接超时异常就是一个定时任务,若是在指定秒数中任务执行则表示连接失败,若是没有执行则说明并没有出现超时连接异常(在时间范围内连接成功并且取消了该定时任务)

image-20220115195824592

image-20220115200052305

定时任务执行情况:在EventLoop中线程定时任务执行时,会调用connectPromise.tryFailure(cause),其就会唤醒sync(),接着捕获异常打印信息,失败了就会进入catch(),成功会继续向后执行channel()。

注意:在线程之间通信使用的是promise!


②SO_BACKLOG(全连接队列中存储连接个数)

参数说明+代码示例

参数说明+例子

来源:属于ServerSocketChannal 参数。

client server syns queue accept queue bind() listen() connect() 1. SYN SYN_SEND put SYN_RCVD 2. SYN + ACK ESTABLISHED 3. ACK put ESTABLISHED accept() client server syns queue accept queue
  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(全连接队列)
    • 最终当进行accept()时,就会从队列中取出来连接!

该参数与三次握手相关,若是三次握手成功,则会从全连接队列中取出来!

  • 队列存在意义:在accept()实际处理前会在队列中堆积。可用于减轻服务端accept()的压力,用于临时保存一些连接状态,三次握手是发生在accept()之前的。

backlog:指的就是半连接、全连接队列的数量,全连接队列大小决定了你有多少个客户端能够在队列中临时存放,若是客户端的连接大于队列的个数,也就是处理连接能力达到上限了,就会出现拒绝连接的错误!

实际使用方式

  1. 可通过linux系统配置,若是windows环境直接程序配置即可!

    在 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
    
  2. 程序控制方式。对于NIO中ServerSocketChannel其其通过bind(8080,backlog)来设置;对于netty通过配置参数如option(ChannelOption.SO_BACKLOG,1024)

程序默认值说明:若是我们不进行手动配置,其也会有默认参数值,对于nio,可通过bind()方法,借助IDEA的useage快捷键,顺着调用方法向下找,最终能够在NetUtil中的静态代码块找到赋值操作;对于windows平台会设置为200,非windows为128,接着会去尝试读取linux的配置文件属性值!

测试程序方式:通过断点debug,让其阻塞在read()方法中,那么连接的客户端就会被放入到队列里,此时就能够测试出连接拒绝异常的效果了!

调参建议:若是设置其过小,那么在高峰期间大量客户端连接就会出现连接拒绝的问题!应该尽量对backlog设置大一点,让队列不容易被填满,这样客户端连接才不会被大量拒绝!

代码示例

对于netty设置参数的例子:是对serversocketchannel的设置的参数

public class Server {

    public static void main(String[] args) throws InterruptedException {
        new ServerBootstrap()
                .group(new NioEventLoopGroup(),new NioEventLoopGroup())
                .option(ChannelOption.SO_BACKLOG, 2) //设置全连接队列个数为2
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                    }
                }).bind(8081).sync();
        System.out.println("服务器已启动!");
    }

}

源码分析

①参数设置的效果演示

对netty进行调试的话,我们需要在NioEventLoop类里的processSelectedKey方法中的unsafe.read()打上断点,该方法的含义就是从全连接队列中取出连接请求:

image-20220115221642453

②backlog的默认值:若是不设置其默认值?

从NIO中ServerSocketChannel的bind(地址,backlog)入手

image-20220115222631016

image-20220115222754283

查看其实现:

image-20220115222956811

在其父类DefaultServerSocketChannelConfig中有getBacklog的具体实现:

image-20220115223208558

最终在NetUtil的静态代码块中可以找到其赋值操作:

image-20220115223405333


backlog其他参数

ulimit -n 数字

来源:属于操作系统参数。

限制你一个线程能够同时打开的最大文件描述符的数量(可以是socket连接),linux中无论socket、文件都是使用的一个文件描述符表示,简称FD,当文件描述符达到上限,再想打开就会报一个错误,其上限是为了保护你的系统打开的socket数不要太多。

建议:若是你的服务器想要应对高并发,支持大量的连接连我们服务器,就一定要调整这一个参数。这个参数属于一个临时类调整,建议把它放在启动脚本里。


③tcp_nodelay(一次尽可能发出数据,而非缓冲区缓存;禁用nagle算法)

参考文章:ChannelOption.TCP_NODELAY, true->浅谈tcp_nodelay的作用

来源:属于 SocketChannal 参数。

说明:为了尽可能发送大块数据,避免网络中充斥着许多小数据块。根据某个算法会先对要发的数据进行攒一批之后一次发送出去。默认设置的是false(开启的nagle算法),实际希望消息尽快发出去,所以要设置其为true。(对于想要测试出效果,目前没有找到合适的例子)

.option(ChannelOption.TCP_NODELAY,true)

④so_sendbuf & so_rcvbuf(发送缓冲区和接收缓冲区)

来源:SO_SNDBUF 属于 SocketChannal 参数;SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)

目的:设置了滑动窗口的上限。建议不要调整该参数,早期应该根据参数来进行调整,但现在的操作系统比较智能,会自动的根据通信双方的网络能力调整sendbuf和rcvbuf。并不是调的越大越好,若是调的太大可能会对系统内存的占用会比较高!


⑤allocator(配置ctx.alloc()分配的类型和开辟内存空间位置)

参数说明

来源:属于 SocketChannal 参数。

主要目的:配置ctx.alloc()来创建的类型(池化、非池化)与开辟位置(直接、堆)。

默认配置说明:默认是池化直接内存。实际上是根据一个配置类的中配置类型来进行设置的。

配置方式:通过配置虚拟机参数,io.netty.allocator.type=pooled|unpooled来设置池化、非池化。对于直接或堆内依旧使用一个系统变量io.netty.noPrefferDirect=true|false。

测试

直接进行虚拟机参数配置即可:

image-20220115232603547

ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.debug("ctx.alloc() => {}", ctx.alloc().buffer());
    }
});

image-20220115232659145


源码调试

起点:ChannelConfig

image-20220115231650260

image-20220115231826515

配置池化、非池化参数:io.netty.allocator.type=unpooled|pooled

image-20220115232228704

image-20220115232259847

配置直接、堆内存:io.netty.noPreferDirect=false|true

image-20220115232340986


⑥rcvbuf_allocator(控制netty的接收缓冲区大小。就是在readchannel事件中读到的ByteBuf。)

参数说明+代码示例

来源:属于 SocketChannal 参数。

效果:控制netty的接收缓冲区大小。就是在readchannel事件中读到的ByteBuf。

核心点及默认配置:控制netty的接收缓冲区大小,指的就是最初readchannel中读到的ByteBuf。默认容量为1024,之后实际开辟空间大小跟后面几次传输的实际大小相关,最大为65536,最小为64。

  • 关于类型与开辟空间位置:类型依旧根据根据虚拟机参数io.netty.allocator.type=pooled|unpooled来配置!分配空间位置默认是直接内存,这是固定的不能够更改。(netty认为使用直接内存效率更高)

代码示例

//设置指定的接收ByteBuf大小为100字节
.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(100))

测试:

image-20220115234137459

image-20220115234914834

源码分析

切入点:通过设置断点,来根据调用栈找到

image-20220115235519746

image-20220115235640211

image-20220116000016367

其控制池化、非池化:

image-20220116000126198

接着看allocate分配空间方法:
image-20220116000253182

image-20220116000348912

说明:实际由一个分配器来进行创建的ByteBuf。该分配器只能用于配置是池化还是非池化。分配的时候调用的就是ioBuffer,指定就是直接内存,对于容量大小调用的是guess()方法其是猜测出来的,其会动态的根据你几次的数据量来对创建的ByteBuf进行调整,最开始默认是1024,之后若是数据量增多会进行调整,最大不会超过65536。若是穿过来的数据量很小,那么也会进行减少,最小为64。

对于分配的细节,我们需要来查看allocHandle

image-20220116000647234

接着依次查看调用方,确定源头:对应三个参数值对应上面的说明

image-20220116000836111

image-20220116001030616

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。