• Day705.Tomcat拒绝连接原因分析及网络优化 -深入拆解 Tomcat & Jetty


    Tomcat拒绝连接原因分析及网络优化

    Hi,我是阿昌,今天记录学习的是关于Tomcat拒绝连接原因分析及网络优化的内容。

    一、常见异常

    1、java.net.SocketTimeoutException

    指超时错误。超时分为连接超时读取超时,连接超时是指在调用 Socket.connect 方法的时候超时,而读取超时是调用 Socket.read 方法时超时。

    注意的是,连接超时往往是由于网络不稳定造成的,但是读取超时不一定是网络延迟造成的,很有可能是下游服务的响应时间过长。

    2、java.net.BindException: Address already in use: JVM_Bind

    指端口被占用。当服务器端调用 new ServerSocket(port) 或者 Socket.bind 函数时,如果端口已经被占用,就会抛出这个异常。我们可以用netstat –an命令来查看端口被谁占用了,换一个没有被占用的端口就能解决。

    3、java.net.ConnectException: Connection refused: connect

    指连接被拒绝。当客户端调用 new Socket(ip, port) 或者 Socket.connect 函数时,可能会抛出这个异常。

    原因是指定 IP 地址的机器没有找到;或者是机器存在,但这个机器上没有开启指定的监听端口。

    解决办法是从客户端机器 ping 一下服务端 IP,假如 ping 不通,可以看看 IP 是不是写错了;假如能 ping 通,需要确认服务端的服务是不是崩溃了。

    4、java.net.SocketException: Socket is closed

    指连接已关闭。出现这个异常的原因是通信的一方主动关闭了 Socket 连接(调用了 Socket 的 close 方法),接着又对 Socket 连接进行了读写操作,这时操作系统会报“Socket 连接已关闭”的错误。

    5、java.net.SocketException: Connection reset/Connect reset by peer: Socket write error

    指连接被重置。这里有两种情况,分别对应两种错误:

    第一种情况是通信的一方已经将 Socket 关闭,可能是主动关闭或者是因为异常退出,这时如果通信的另一方还在写数据,就会触发这个异常(Connect reset by peer);

    如果对方还在尝试从 TCP 连接中读数据,则会抛出 Connection reset 异常。

    • 为了避免这些异常发生,在编写网络通信程序时要确保:

    • 程序退出前要主动关闭所有的网络连接。检测通信的另一方的关闭连接操作,当发现另一方关闭连接后自己也要关闭该连接。

    6、java.net.SocketException: Broken pipe

    指通信管道已坏。发生这个异常的场景是,通信的一方在收到“Connect reset by peer: Socket write error”后,如果再继续写数据则会抛出 Broken pipe 异常,解决方法同上。

    7、java.net.SocketException: Too many open files

    指进程打开文件句柄数超过限制。当并发用户数比较大时,服务器可能会报这个异常。

    这是因为每创建一个 Socket 连接就需要一个文件句柄,此外服务端程序在处理请求时可能也需要打开一些文件。你可以通过lsof -p pid命令查看进程打开了哪些文件,是不是有资源泄露,也就是说进程打开的这些文件本应该被关闭,但由于程序的 Bug 而没有被关闭。

    如果没有资源泄露,可以通过设置增加最大文件句柄数。具体方法是通过ulimit -a来查看系统目前资源限制,通过ulimit -n 10240修改最大文件数。

    在这里插入图片描述

    二、Tomcat 网络参数

    接下来我们看看 Tomcat 两个比较关键的参数:maxConnections 和 acceptCount。在解释这个参数之前,先简单回顾下 TCP 连接的建立过程:客户端向服务端发送 SYN 包,服务端回复 SYN+ACK,同时将这个处于 SYN_RECV 状态的连接保存到半连接队列。客户端返回 ACK 包完成三次握手,服务端将 ESTABLISHED 状态的连接移入 accept 队列,等待应用程序(Tomcat)调用 accept 方法将连接取走。这里涉及两个队列:

    • 半连接队列:保存 SYN_RECV 状态的连接。队列长度由net.ipv4.tcp_max_syn_backlog设置。
    • accept 队列:保存 ESTABLISHED 状态的连接。队列长度为min(net.core.somaxconn,backlog)。其中 backlog 是我们创建 ServerSocket 时指定的参数,最终会传递给 listen 方法:
    int listen(int sockfd, int backlog);
    
    • 1

    如果我们设置的 backlog 大于net.core.somaxconn,accept 队列的长度将被设置为net.core.somaxconn,而这个 backlog 参数就是 Tomcat 中的 acceptCount 参数,默认值是 100,但请注意net.core.somaxconn的默认值是 128。

    可以想象在高并发情况下当 Tomcat 来不及处理新的连接时,这些连接都被堆积在 accept 队列中,而 acceptCount 参数可以控制 accept 队列的长度,超过这个长度时,内核会向客户端发送 RST,这样客户端会触发上文提到的“Connection reset”异常。而 Tomcat 中的 maxConnections 是指 Tomcat 在任意时刻接收和处理的最大连接数。

    当 Tomcat 接收的连接数达到 maxConnections 时,Acceptor 线程不会再从 accept 队列中取走连接,这时 accept 队列中的连接会越积越多。maxConnections 的默认值与连接器类型有关:NIO 的默认值是 10000,APR 默认是 8192。

    会发现 Tomcat 的最大并发连接数等于 maxConnections + acceptCount。

    如果 acceptCount 设置得过大,请求等待时间会比较长;如果 acceptCount 设置过小,高并发情况下,客户端会立即触发 Connection reset 异常。

    三、Tomcat 网络调优实战

    通过一个直观的例子理解。

    我们先重现流量高峰时 accept 队列堆积的情况,这样会导致客户端触发“Connection reset”异常,然后通过调整参数解决这个问题。

    主要步骤有:

    下载和安装压测工具JMeter。解压后打开,我们需要创建一个测试计划、一个线程组、一个请求和,如下图所示。

    测试计划:

    在这里插入图片描述

    线程组(线程数这里设置为 1000,模拟大流量):

    在这里插入图片描述

    请求(请求的路径是 Tomcat 自带的例子程序):

    在这里插入图片描述
    启动 Tomcat。

    开启 JMeter 测试,在 View Results Tree 中会看到大量失败的请求,请求的响应里有“Connection reset”异常,也就是前面提到的,当 accept 队列溢出时,服务端的内核发送了 RST 给客户端,使得客户端抛出了这个异常。

    在这里插入图片描述

    修改内核参数,在/etc/sysctl.conf中增加一行net.core.somaxconn=2048,然后执行命令sysctl -p

    修改 Tomcat 参数 acceptCount 为 2048,重启 Tomcat。

    在这里插入图片描述
    再次启动 JMeter 测试,这一次所有的请求会成功,也看不到异常了。

    我们可以通过下面的命令看到系统中 ESTABLISHED 的连接数增大了,这是因为我们加大了 accept 队列的长度。

    在这里插入图片描述

    四、总结

    在 Socket 网络通信过程中,不可避免地会碰到各种 Java 异常,了解这些异常产生的原因非常关键,通过这些信息我们大概知道问题出在哪里,如果一时找不到问题代码,我们还可以通过网络抓包工具来分析数据包。

    在这个基础上,分析了 Tomcat 中两个比较重要的参数:acceptCountmaxConnections

    acceptCount 用来控制内核的 TCP 连接队列长度,maxConnections 用于控制 Tomcat 层面的最大连接数。

    通过调整 acceptCount 和相关的内核参数somaxconn增加了系统的并发度

    在上面的实验中,我们通过netstat命令发现有大量的 TCP 连接处在 TIME_WAIT 状态,请问这是为什么?它可能会带来什么样的问题呢?

    TCP 连接处在 TIME_WAIT 状态,这个是TCP协议规定的,四次挥手时主动关闭方所处的一个状态,会等待2个MSL,所以在这个时间段内不会释放句柄,如果并发量大的话,会导致句柄不够用,从而影响新的TCP连接。


  • 相关阅读:
    Vue学习:分析hello案例
    lambda 的组成部分 operator==类体和全局中的细微区别 哈希容器代码 自定义哈希函数
    3.1_2 覆盖与交换
    枚举类型原来是这么回事儿
    【优化求解】整数规划求解机票超售优化赔付问题【含Matlab源码 2182期】
    带你一起玩转—Java 数组
    设计模式(二)-创建者模式(4)-原型模式
    从 Linux 安装到 Hadoop 环境搭建全过程
    SpringCloudAlibaba组件 — — OpenFeign的其他作用【超时时间、日志打印】
    【毕业设计】1-1Matlab小电流接地系统的建模与单相故障的仿真分析(仿真工程文件+结果图+论文+PPT)
  • 原文地址:https://blog.csdn.net/qq_43284469/article/details/126294338