排查 reactor-netty 报错 Connection reset by peer 的过程

文章目录

  • 1. 报错现象
  • 2. 排查过程
      • 2.1 Connection reset by peer 的原因
      • 2.2 syscall:read(..) failed: Connection reset by peer 错误
  • 3. 最终原因

1. 报错现象

组内一个服务从 spring-webmvc 框架切换到 spring-webflux,在线上跑了一段时间后偶现如下错误 log 。log 中 L:/10.0.168.212:8805 代表了本地服务所在的服务器 IP 和 端口,R:/10.0.168.38:47362 表示发起请求的服务所在的服务器 IP 和 端口,整体看错误似乎是 服务端读取从 10.0.168.38:47362 发起的请求数据时,因为对端连接被重置的原因失败了。这种情况常见的原因是服务端繁忙,然而检查服务调用的监控,发现调用量正常,并不足以构成服务繁忙的条件

2020-05-110 10:35:38.462 ERROR reactor-http-epoll-1 [] reactor.netty.tcp.TcpServer.error(300) - [id: 0x230261ae, L:/10.0.168.212:8805 - R:/10.0.168.38:47362] onUncaughtException(SimpleConnection{channel=[id: 0x230261ae, L:/10.0.168.212:8805 - R:/10.0.168.38:47362]})
io.netty.channel.unix.Errors$NativeIoException: syscall:read(..) failed: Connection reset by peer
	at io.netty.channel.unix.FileDescriptor.readAddress(..)(Unknown Source)

2. 排查过程

2.1 Connection reset by peer 的原因

这种错误几乎没有遇到过,首先想到的当然是网上搜索错误关键字,然后找到了如下内容。很明显Connection reset by peer 就是服务端在对端 Socket 连接关闭后仍然向其传输数据引起的,但是对端关闭连接的原因却是未知

异常 原因
java.net.BindException:Address already in use: JVM_Bind 该异常发生在服务器端进行new ServerSocket(port)操作时,原因是本地端口已经被其他程序占用。此时用netstat –an命令,可以看到本地已在使用状态的端口, 使用一个没有被占用的端口就能解决这个问题
java.net.ConnectException: Connection refused: connect 该异常发生在客户端进行 new Socket(ip, port)操作时,原因是无法找到目标 ip 地址的服务端(也就是从当前机器不存在到指定 ip 的路由),或者是该 ip 存在,但目标服务器上指定的端口没有程序监听
java.net.SocketException: Socket is closed 该异常在客户端和服务器均可能发生,原因是己方主动关闭了连接后(调用了 Socket#close() 方法)再对网络连接进行读写操作
java.net.SocketException: (Connection reset 或者 Connect reset by peer) 该异常在客户端和服务器端均有可能发生,大致分为两个,第一个是当前端的 Socket 收到对端的 RST 报文后仍然读数据(对端因为异常退出而引起关闭会发送 RST,例如 Socket 设置读超时 60 秒,60秒之内没有心跳交互就会触发超时,异常关闭连接),引发 Connect reset by peer异常。另一个是,TCP 两端已经互发 FIN 报文正常关闭连接,但其中一端仍然使用该连接读写数据,引发 Connection reset异常简单说,这两个异常就是在 TCP 连接断开后的读写操作引起的
java.net.SocketException: Broken pipe 该异常在客户端和服务器均有可能发生,当前端在读写数据前断开连接(如当前端的程序准备写入数据到 Socket,结果发起IO调用后程序异常退出),则抛出该异常

2.2 syscall:read(…) failed: Connection reset by peer 错误

继续搜索其他关键字,然后兜兜转转找到了 github 上 reactor-netty 的 issue。github 上其他开发者贴出的报错内容与笔者遇到的几乎完全一致,仔细阅读下来,发现其他开发者遇到这个问题主要是以下两种解决方式:

  • 禁用长连接
  • 修改负载均衡策略为最小连接数策略

从 comment 来看,这主要是涉及到了 reactor-netty 的连接池机制。我们知道 netty是基于 nio (参考Java IO模型及示例)的框架,它在处理连接请求的时候使用了一个连接池来保证并发吞吐。通过定制 ClientHttpConnector的长连接属性为 false ,保证了连接池线程不被长时间占用,这种方法在其他开发者使用的场景中似乎能有效解决这个错误

3. 最终原因

查看 github 上的 comment,总觉得其他开发者的场景与我们并不完全一致,但是一时也没有什么思路。leader 在内部群里喊了一声,到了晚上终于有同事从 log 中发现了端倪。因为服务中有打印 SQL 语句的插件,通过 log 发现有一条语句执行了整整 60s,而执行该语句的线程与之后报出错误的线程号一致,至此一切豁然开朗

  • reactor-netty 连接池分配了线程reactor-http-epoll-1处理一个请求Areactor-http-epoll-1处理过程中因为慢 SQL 一直阻塞了 60s,在此期间同一个接口被高频率访问,连接池中的其他线程也被分配来处理同一类请求,然后也因为慢 SQL 阻塞住。在连接池中的线程都被阻塞住的时候,新的请求过来,连接池中已经没有线程可以对其进行处理,请求端因此一直被 hold,直到超时后主动关闭了 Socket。这之后服务端连接池线程终于处理完慢 SQL 请求,再来处理积压的请求,完成后把数据发送往请求端,却发现连接已经被关闭,就报出了Connection reset by peer 错误。本次排查得到的经验是,如果服务报出 Connection reset by peer 错误,首先检查是不是服务中有执行特别慢的动作阻塞了线程

分析慢 SQL 发现,那条语句之所以执行耗时如此之长,是因为 MySQL 数据库中数据类型为 VARCAHR 的字段接受了 Long 数据类型的条件,造成了隐式类型转换,无法使用索引,进而引发了全表扫描。

你可能感兴趣的:(随笔,java,netty)