java.net.SocketInputStream.socketRead0 卡死导致 tomcat 线程池打满的问题

0 TL;DR;

  • 问题与原因:某些特定条件下 java.net.SocketInputStream.socketRead0 方法会卡死,导致运行线程一直被占用导致泄露
  • 采用的方案:使用监控线程异步监控卡死事件,如果发生直接关闭网络连接释放链接以及对应的线程

1. 问题

一个服务 tomcat 线程池线程总是不释放,之前只能靠重启服务缓解
(这个服务的作用是对第三方网站做一个类似于适配器模式的封装,简单的说就是请求打到该服务,该服务请求第三方网站,将数据组织成需要的格式返回,是整个爬虫系统的一个环节)
在这里插入图片描述

2. 定位

jstack 导出 stack.info,观察这些卡死的 tomcat 线程在做什么

第一类状态如下,这种状态是 tomcat 空闲线程,状态是 TIMED_WAITING 在等待新任务到来进行处理

"http-nio-8080-exec-1810" #16955528 daemon prio=5 os_prio=0 tid=0x00007f2de4707000 nid=0x239136 waiting on condition [0x00007f2700887000]java.lang.Thread.State: TIMED_WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x00000001c31000e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:89)at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:33)at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:750)

第二类状态如下,这种状态是 tomcat 在执行某项工作,状态是 RUNNALBE

如果反复观察某些特定的线程状态(例如这里的 http-nio-8080-exec-1811)通过 State 是否会改变以及业务日志是否卡在某个位置之后不动了,基本就可以定位哪些线程出了问题

"http-nio-8080-exec-1811" #16955529 daemon prio=5 os_prio=0 tid=0x00007f2de4709000 nid=0x239137 runnable [0x00007f2700784000]java.lang.Thread.State: RUNNABLEat java.net.SocketInputStream.socketRead0(Native Method)at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)at java.net.SocketInputStream.read(SocketInputStream.java:171)at java.net.SocketInputStream.read(SocketInputStream.java:141)at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)at org.apache.http.impl.execchain.MainClientExec.createTunnelToTarget(MainClientExec.java:485)at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:410)at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
... (省略)

最终发现,线程卡在了 java.net.SocketInputStream.socketRead0(Native Method),那么其含义是什么呢?

3. 原因与方案

参考如下文章:https://medium.com/tier1app-com/threads-stuck-in-java-net-socketinputstream-socketread0-d0a2183b4a1c

可以想象你给一个人打电话的场景,她接了电话但是有的时候并没有说话,而是你在等待她说话。那么从电话打通到电话挂断,你等待她说话的时间基本都是 socketRead0() API 在做的事情

由于这是一个底层的方法,所以很多应用都会用到这个方法。当你的应用一直无法读取到完整的数据时,就会看起来卡在了 socketRead0() 这个方法上

那么这个问题该如何解决呢,面的参考资料提供了一些方案,我还参考了另外一部分可行方案方案(来自:https://stackoverflow.com/questions/28785085/how-to-prevent-hangs-on-socketinputstream-socketread0-in-java),汇总如下

3.1 设置合适的参数

jvm 参数:

  • Dsun.net.client.defaultConnectTimeout
  • Dsun.net.client.defaultReadTimeout

代码层面的层参数

  • setSoTimeout
  • setStaleConnectionCheckEnabled(用于清理长时间占用的链接,已经过时废弃,目前直接默认开启的)

备注:有人指出,这是 JVM 在 Linux 上实现阻塞套接字超时存在 bug,poll 或者 select 可能会错误的通知数据可用的消息,这时除非服务器断开连接,否则将无限期的等待下去。而这种情况无法通过简单的参数设置,解决该问题。

3.2 网络或者服务侧的问题

有的时候可能是因为网络设施、负载均衡或者对方服务本身的问题,导致这一现象,这时应该用一些网络抓包工具(例如 Wireshark)发现并解决这些问题

由于我的服务本身是请求第三方网站,该方案并没有什么帮助

3.3 将网络客户端由阻塞替换为非阻塞客户端

可以使用 Grizzly 或者 Netty 客户端,来替换原有的 http 客户端(我是用的是 httpclient),但这通常涉及到整体系统的重构和测试,代码改动量过大

3.4 单独启动线程检测处理超时,如果超时就想办法中断处理流程

这是一个虽然丑陋但是可靠的方案,也是我所采用的方案。逻辑简单,增加监控线程,处理那些卡死的线程。

4. 示例代码

逻辑是每次请求之前调用 addToWatch 方法异步的监控是否在合理的时间范围内 HttpClient 已经关闭了

如果超过了超时时间,就直接关闭 HttpClient,这样原本处于等待状态的 java.net.SocketInputStream.socketRead0 会接收到中断而终止(这个中断消息是我猜的,但是实际来看是有效的)


@Slf4j
public class HttpClientWatcher {private static final ThreadPoolExecutor WATCH_THREAD_POOL = new ThreadPoolExecutor(20, 50, 1000L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(10000),new ThreadPoolExecutor.DiscardPolicy());@Data@Builderstatic class CloseableHttpClientWrapper {private CloseableHttpClient httpClient;@SuppressWarnings("UnusedAssignment")private volatile boolean closed = false;}public static void addToWatch(CloseableHttpClientWrapper wrapper, int timeoutMillis) {if (wrapper == null || wrapper.getHttpClient() == null || wrapper.isClosed()) {return;}WATCH_THREAD_POOL.execute(() -> watch(wrapper, timeoutMillis));// 打印线程池状态,用来调整线程池参数log.info("In addToWatch, activeCount: {}, poolSize: {}, queueSize: {}", WATCH_THREAD_POOL.getActiveCount(),WATCH_THREAD_POOL.getPoolSize(), WATCH_THREAD_POOL.getQueue().size());}public static void watch(CloseableHttpClientWrapper wrapper, int timeoutMillis) {final long timeoutTimestamp = System.currentTimeMillis() + Math.min(10L * timeoutMillis, 10 * 60 * 1000L);while (System.currentTimeMillis() < timeoutTimestamp) {if (wrapper.isClosed()) {return;}ThreadUtil.sleep(50, TimeUnit.MILLISECONDS);}// 这里单独判断一次,是因为担心在 sleep 的时候,httpClient 已经被关闭了if (wrapper.isClosed()) {return;}// 超时尝试关闭try {wrapper.getHttpClient().close();} catch (Exception e) {log.error("关闭HttpClient失败", e);}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/9177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nacos下载安装和nacos启动报错

nacos简介: Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您…

YOLOv9中模块总结补充|RepNCSPELAN4详图

专栏地址&#xff1a;目前售价售价69.9&#xff0c;改进点70 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 1. RepNCSPELAN4详图 RepNCSPELAN4是YOLOv9中的特征提取-融合模块&#xff0c;类似前几…

【数据结构-二叉搜索树的增删查改】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a;基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 …

[虚拟机+单机]梦幻契约H5修复版_附GM工具

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 视频演示 [虚拟机单…

vue2人力资源项目2登录接口、跳转主页

用户名和密码 解决跨域问题 1.在vue.config.js里配置 proxy: {// 如果请求地址里有api&#xff0c;就转成这个地址/api: {target: https://heimahr.itheima.net/}}// before: require(./mock/mock-server.js)}, axios封装 utils/request.js import axios from axios// creat…

Linux-信号执行

1. 信号什么时候被处理 当进程从内核态返回到用户态的时候&#xff0c;进行信号的检测和处理 什么内核态&#xff0c;什么又是用户态呢&#xff1f; 当进程在CPU上运行时&#xff0c;内核态&#xff1a;允许进程访问操作系统的代码和数据&#xff0c;用户态&#xff1a;进程只…

户口本翻译件怎么处理?

户口本是中国公民的重要证件&#xff0c;由中华人民共和国公安部精心制作&#xff0c;不仅是国内身份的凭证&#xff0c;更是走向世界的一张关键名片。对于想出国留学 、追求移民生活或是追寻异国风情的旅行者来说&#xff0c;户口本翻译件都是不可或缺的一部分。那么&#xf…

Duplicate entry ‘asdfg‘ for key ‘clazz.name‘

Mybatis:java.sql.SQLIntegrityConstraintViolationException:Duplicate entry ‘asdfg’ for key ‘clazz.name’ 违反了数据库的唯一约束条件&#xff0c;即插入数据的时候具有唯一约束&#xff08;被unique修饰&#xff09;的列值重复了 在修改的过程中发生错误&#xff0c;…

【LLM 论文】Least-to-Most Prompting 让 LLM 实现复杂推理

论文&#xff1a;Least-to-Most Prompting Enables Complex Reasoning in Large Language Models ⭐⭐⭐ Google Research, ICLR 2023 论文速读 Chain-of-Thought&#xff08;CoT&#xff09; prompting 的方法通过结合 few-show prompt 的思路&#xff0c;让 LLM 能够挑战更具…

蓝桥青少一月 STEMA-Python 测评第一题

第一题&#xff08;难度系数 2&#xff0c;18 个计分点&#xff09; (注.input()输入函数的括号中不允许添加任何信息) 编程实现&#xff1a; 给定一个正整数 N&#xff0c;输出 N 除以 3 的商。 输入描述&#xff1a;输入一个正整数 N 输出描述&#xff1a;输出 N 除以 3 的商…

2024年抖音小店最新起店玩法,比你报的上万课程都有用!

大家好&#xff0c;我是电商糖果 刚开店的朋友&#xff0c;一定会遇到出单难&#xff0c;店铺没有流量的问题。 自己在网上找一堆教程&#xff0c;或者花高价去报课程。 有的朋友比较幸运&#xff0c;遇到了好的领路人&#xff0c;但是大部分朋友还是没有那么幸运的。 糖果…

API低代码平台介绍2-最基本的数据查询功能

最基本的数据查询功能 本篇文章我们将介绍如何使用ADI平台定义一个基本的数据查询接口。由于是介绍平台具体功能的第一篇文章&#xff0c;里面会涉及比较多的概念介绍&#xff0c;了解了这些概念有助于您阅读后续的文章。 ADI平台的首页面如下&#xff1a; 1.菜单介绍 1.1 O…

【协同过滤】ItemCF协同过滤方法简介

一、ItemCF协同过滤方法 ItemCF 是基于物品相似度进⾏推荐的协同过滤算法。 通过计算共现矩阵中物品列向量的相似度得到物品之间的相似矩阵&#xff0c; 再找到⽤户的历史正反馈物品的相似物品进⾏进⼀步排序和推荐&#xff0c;Item CF的具体步骤如下&#xff1a; 构建共现矩…

测试项目实战——安享理财1(测试用例)

说明&#xff1a; 1.访问地址&#xff1a; 本项目实战使用的是传智播客的安享理财项目&#xff08;找了半天这个项目能免费用且能够满足测试实战需求&#xff09; 前台&#xff1a;http://121.43.169.97:8081/ 后台&#xff1a;http://121.43.169.97:8082/ &#xff08;点赞收藏…

Python turtle绘制图形详解

Python 的 Turtle 模块是一个简单而直观的绘图工具&#xff0c;可以帮助初学者理解基本的图形绘制概念。 1.导入 Turtle 模块&#xff1a; import turtle 2.创建 Turtle 对象&#xff1a; t turtle.Turtle() 3.绘制图形&#xff1a; 4.移动Turtle对象&#xff1a;t.forward(di…

点击短信链接唤起Android App实战

一.概述 在很多业务场景中,需要点击短信链接跳转到App的指定页面。在Android系统中,想要实现这个功能,可以通过DeepLink或AppLink实现。二.方案 2.1 DeepLink 2.1.1 方案效果 DeepLink是Android系统最基础、最普遍、最广泛的外部唤起App的方式,不受系统版本限制。当用户…

腾讯云优惠券领取指导及优惠券使用指南详解

在当今云计算市场&#xff0c;腾讯云以其出色的性能和服务质量受到了广大用户的青睐。为了回馈用户&#xff0c;腾讯云经常推出各种优惠活动&#xff0c;其中就包括优惠券的发放。那么&#xff0c;如何领取腾讯云优惠券&#xff0c;并正确地使用它们呢&#xff1f;本文将为您详…

虚拟机镜像文件qcow2格式转vmdk

一、在esxi上虚拟机导出qcow2镜像文件 1、卸载数据盘、网卡 2、登录虚拟机所在物理服务器&#xff0c;查找系统盘名为vm-101-disk-0的文件位置 find / -name "vm-101-disk-0"使用命令导出qcow2镜像&#xff08;进度条走完就完成了&#xff09;&#xff1a; qemu…

JAVA----Thread(2

Thread 提供的属性和方法 目录 Thread 提供的属性和方法一.构造方法1.Thread() :2.Thread(Runnable target) :3.Thread(String name) :main 线程 4.Thread(Runnable target, String name) : 二.属性1.ID (getId)2.名称(getName)3.状态(getState)4.优先级 (getPriority)5.是否后…

leetcode-岛屿数量-99

题目要求 思路 1.使用广度优先遍历&#xff0c;将数组中所有为1的元素遍历一遍&#xff0c;遍历过程中使用递归&#xff0c;讲该元素的上下左右四个方向的元素值也置为0 2.统计一共执行过多少次&#xff0c;次数就是岛屿数量 代码实现 class Solution { public:int solve(vec…