一次现网事故的排查--connection reset by peer

背景描述

应用介绍

应用使用spring boot,并使用内嵌的tomcat,即使用java -jar xxx.jar启动应用的,区别于前几年,将应用打成war包并放到tomcat的webpp目录。

问题描述

  1. 应用本地缓存了许多业务数据,导致日志中出现OOM报错。
  2. OOM发生后,API网关所有路由到这台机器的请求,都是发生超时。
  3. 通过单边地址访问(使用curl命令),也是发生超时,请求得不到相应,同时报错connection reset by peer。

排查过程

检查应用层状态

  1. 检查进程是否挂了
    通过命令ps -ef -w w,检查应用的进程是否还存在;     --检查未发现异常
  2. 检查是否发生死锁
    通过jstack pid,检查是否出现线程死锁;              --检查未发现异常

检查OS层状态

  1. 检查系统CPU利用率,是否因为应用(不仅本应用,也可能是其他应用)有BUG出现死循环,导致CPU利用率100%,应用的请求无法及时得到cpu资源,响应延时很大,导致超时
    通过命令top,查看cpu利用率是否有异常        --检查未发现异常
  2. 检查网络层是否出现异常,因为是在云上运行的,可能网络层出现状况
    通过命令telnet检测端口能否访问通          --检查未发现异常
  3. 检查系统连接状态,可能有大量的连接请求未释放,超过系统的限制,导致新的请求被丢弃或者阻塞
    使用命令netstat -natl                --发现异常
    在这里插入图片描述
    LISTEN状态的Recv-Q代表连接已经过三次握手,等待应用的代码调用accept。
    多次调用netstat -natl,发现该数值不会变化。 怀疑连接数超出限制,新请求无法接入
  4. 确认是否进程的连接数超出限制
    使用命令netstat -s | grep listen,结果如下
    85 times the listen queue of a socket overflowed
    使用命令netstat -s | grep LISTEN,结果如下
    85 SYNs to LISTEN sockets ignored
    使用命令ss -lt,结果如下
    在这里插入图片描述

通过三条命令发现,确实是连接数超出了限制。tomcat默认的backlog为100(可以到tomcat官网查看,或者搜索tomcat源码“acceptCount”跟踪)。
截图的中的Send-Q代表Accept队列的大小,Resv-Q代表当前Accept队列中已有的连接数。从截图来看Accept队列已经满了,新连接没办法在完成TCP三次握手,进入到这个队列。
在第三次tcp握手时,由于Accept队列满了,客户端发起ack包时,服务端直接回复RST包,所以客户端得到connection reset by peer的报错。 --【Accept队列满了,如何回应ACK包由/proc/sys/net/ipv4/tcp_abort_on_overflow决定,0表示扔掉ACK包,1表示回复RST包】

进一步分析

找出为什么应用层不处理新请求

通过前面步骤发现,cpu利用率很低(通过top可以看到),但是进来的请求仍然得不到处理(问题解决,已经被隔离了,不会有新请求过来,但是通过netstat 发现LISTEN状态的Recv-Q的数量没有发生变化),猜测内嵌的tomcat发生了什么故障,请求无法传递到业务层被处理。
下一步应该是对比正常节点和该故障节点tomcat线程的差异,通过对比发现异常节点,少了截图中线程
在这里插入图片描述
从堆栈信息可以看到,该线程是accept客户端的请求的,定位到这里就已经发现请求为啥得不到处理了。

tomcat的线程为什么会挂掉

直觉上,调用accept出现异常时,应该catch掉,因为单个请求accept失败,并不会影响全局。
持着怀疑的态度,翻阅的tomcat的源码(org.apache.tomcat.util.net.Acceptor),代码有删减
前面提到应用本地缓存了很多数据,导致OOM,所以这里抛出java.lang.OutOfMemoryError,该异常是VirtualMachineError的子类。
TOMCAT并未细分VirtualMachineError,而是做了统一处理;既然是虚拟机出现了问题,那应用层无能为力,就不用catch住了。这个逻辑也说的通,避免错误处理过于复杂。
所以从下面代码中可以看到,tomcat遇到VirtualMachineError异常时,直接抛出,导致线程挂掉。

    @Override
    public void run() {
        while (endpoint.isRunning()) {
            state = AcceptorState.RUNNING;

            try {
                U socket = null;
                socket = endpoint.serverSocketAccept();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                String msg = sm.getString("endpoint.accept.fail");
            }
        }
        state = AcceptorState.ENDED;
    }
        public static void handleThrowable(Throwable t) {
        if (t instanceof ThreadDeath) {
            throw (ThreadDeath) t;
        }
        if (t instanceof StackOverflowError) {
            return;
        }
        if (t instanceof VirtualMachineError) {
            throw (VirtualMachineError) t;
        }
    }

问题重现

说明

该问题在windows上面无法重现,可能linux和windows在内存管理和网络请求上面的实现有差异,导致JVM虽然内存不足,但是socket = endpoint.serverSocketAccept();这行代码并不会抛异常。这个有精力的人,可以深入分析。

复现步骤

  1. 去spring官网使用initializatier,生成一个简单的spring boot的项目。
  2. 增加一个controller
@RestController
@RequestMapping("test")
public class Controller {

    private List<String> message = new ArrayList<>();
    private boolean flag = true;

    @GetMapping("/get/{message}")
    public String getMessage(@PathVariable("message")String message) {
        return "ok";
    }

    @GetMapping("/oom")
    public String oom() {
        try {
            while(flag) {
                this.message.add(new String("a"));
            }
        } 
        return "ok";
    }
}
  1. mvn package打包,并上传到linux服务器上
  2. linux上启动服务,java -Xms256 -jar xxx.jar
  3. 打开浏览器访问http://{linux的IP}:8080/test/oom
  4. 等到发生OOM后
  5. 浏览器不断的访问http://{linux的IP}:8080/test/get/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    这样就可以复原当时机器的状态
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值