【BUG】生产环境死锁问题定位排查解决全过程

目录

      • 生产环境死锁问题定位排查解决过程
        • 0. 表面现象
        • 1. 问题分析
          • (1)数据库连接池资源耗尽
          • (2)数据库锁竞争
          • (3) 代码实现问题
        • 2. 分析解决
          • (0) 分析过程
          • (1)优化数据库连接池配置
          • (2)优化数据库锁争用
          • (3)优化应用程序
        • 3. 总结

生产环境死锁问题定位排查解决过程

背景:访问项目的生产页面,发现页面上数据加载卡顿,没一会儿有很多接口超时的错误,通过查看服务日志和数据库日志,可以确定是生产数据库死锁了,以下是定位分析并解决死锁的全过程。

根据提供的报错信息和数据库日志,当前服务异常的原因可能是 数据库连接池资源耗尽数据库锁争用。以下是详细分析和解决方案:


0. 表面现象
  • 页面上所有该微服务的请求都无法响应,都是超时失败;

1. 问题分析
(1)数据库连接池资源耗尽
  • 报错信息:

    Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 20, maxActive 20, creating 0, runningSqlCount 10
    
    • active 20, maxActive 20:当前连接池中的所有连接(20 个)都被占用。
    • wait millis 60000:应用程序在等待 60 秒后仍未获取到连接,最终超时。
    • runningSqlCount 10:当前有 10 条 SQL 正在执行。
  • 原因

    • 连接池的最大连接数(maxActive)设置过小,无法满足高并发请求。
    • 某些 SQL 查询执行时间过长,导致连接被长时间占用。
    • 可能存在连接泄漏(未正确关闭连接)。
(2)数据库锁竞争
  • 数据库日志

    00000: 2025-03-24 09:58:55 CST [4101193]: [5-1] user = postgres,db = card_online,remote = 10.246.194.141(45236) app = PostgreSQL JDBC Driver
    DETAIL:  Process holding the lock: 4094261. Wait queue: 4094260, 4094259, 4094258, 4101188, 4094257, 4101189, 4101191, 4101190, 4101192, 4101193, 4101194, 4101195, 4101197, 4101196, 4101198, 4101199, 4101200, 4101201, 4101202.
    
    • Process holding the lock:某个进程(PID: 4094261)持有锁。
    • Wait queue:大量进程(如 4094260、4094259 等)在等待锁。
  • 原因

    • 某个长时间运行的事务或查询持有锁,导致其他事务被阻塞。
    • 锁争用进一步加剧了连接池资源的耗尽。
(3) 代码实现问题
  • 导致数据库死锁所使用的线程池代码
@EnableAsync
@Configuration
public class AsyncPoolConfig implements AsyncConfigurer {/*** 核心线程池大小*/private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;/*** 最大可创建的线程数*/private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 5;/*** 队列最大长度*/private static final int QUEUE_CAPACITY = 1000;/*** 线程池维护线程所允许的空闲时间*/private static final int KEEP_ALIVE_SECONDS = 300;private static final Logger log = LoggerFactory.getLogger(AsyncPoolConfig.class);// 创建线程池@Bean(name = "threadPoolTaskExecutor")public ThreadPoolTaskExecutor threadPoolTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setMaxPoolSize(MAX_POOL_SIZE);executor.setCorePoolSize(CORE_POOL_SIZE);executor.setQueueCapacity(QUEUE_CAPACITY);executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);// 线程池对拒绝任务(无线程可用)的处理策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setThreadNamePrefix("async-task-");return executor;}.....
}

2. 分析解决

业务背景:涉及到的目标模块是一个每天上午10点执行的定时任务,该任务大概内容是从数据库中根据条件查询出相应数据,然后批量插入到另一个数据库表中,涉及到的数据库表数据量大概在 3000w ~ 5000W,原来该定时任务执行的太慢了,后面重构后改为使用线程池并发执行。

(0) 分析过程

根据AsyncPoolConfig 类中的实现,因为服务器是48核的,所以按照代码中的计算公式可得:

CORE_POOL_SIZE = 96
MAX_POOL_SIZE = 480

但是该微服务使用 Druid 管理数据库连接池,最多才20个连接,定时任务开始运行后,线程池中所有线程火力全开,数据库连接池瞬间就被打满了,再加上该微服务其它模块也有数据库连接使用的需求,导致数据库死锁。

(1)优化数据库连接池配置
  • 增加连接池大小
    application.yml 中调整 Druid 连接池的配置:

    spring:datasource:druid:max-active: 50  # 增加最大连接数initial-size: 10min-idle: 10max-wait: 30000  # 减少等待超时时间
    
  • 监控连接池状态
    使用 Druid 的监控功能,检查连接池的使用情况:

    spring:datasource:druid:stat-view-servlet:enabled: trueurl-pattern: /druid/*login-username: adminlogin-password: admin
    

    访问 http://<your-service>/druid,查看连接池的活跃连接、等待连接等信息。

  • 检查连接泄漏
    确保所有数据库连接在使用后正确关闭。可以通过 Druid 的 removeAbandoned 配置检测泄漏连接:

    spring:datasource:druid:remove-abandoned: trueremove-abandoned-timeout: 300  # 超过 300 秒未关闭的连接会被回收
    
(2)优化数据库锁争用
  • 查找持有锁的进程
    在 PostgreSQL 中运行以下查询,查找当前持有锁的进程和等待锁的进程:

    SELECTblocked_locks.pid AS blocked_pid,blocked_activity.usename AS blocked_user,blocking_locks.pid AS blocking_pid,blocking_activity.usename AS blocking_user,blocked_activity.query AS blocked_query,blocking_activity.query AS blocking_query
    FROMpg_catalog.pg_locks blocked_locks
    JOIN pg_catalog.pg_stat_activity blocked_activityON blocked_activity.pid = blocked_locks.pid
    JOIN pg_catalog.pg_locks blocking_locksON blocking_locks.locktype = blocked_locks.locktypeAND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.databaseAND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relationAND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.pageAND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tupleAND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxidAND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionidAND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classidAND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objidAND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubidAND blocking_locks.pid != blocked_locks.pid
    JOIN pg_catalog.pg_stat_activity blocking_activityON blocking_activity.pid = blocking_locks.pid
    WHERENOT blocked_locks.granted;
    
  • 终止阻塞进程
    如果发现某个进程长时间持有锁,可以终止该进程。可通过临时 kill 掉阻塞进程快速恢复生产。要彻底解决掉死锁,还是需要着手业务代码,修改实现,破坏掉构成死锁的条件。

    SELECT pg_terminate_backend(<blocking_pid>);
    
  • 优化慢查询
    检查并优化执行时间较长的 SQL 查询,减少锁持有时间。可以通过以下查询查找慢查询。或者如果你的数据库打开了慢SQL 记录日志,也可以通过数据库日志结合服务日志,根据相应的执行时间查找对应的慢SQL。

    SELECTpid,usename,query,state,now() - query_start AS duration
    FROMpg_stat_activity
    WHEREstate != 'idle'AND now() - query_start > interval '5 minutes'
    ORDER BYduration DESC;
    
(3)优化应用程序
  • 调整线程池参数

    为该任务专门创建了一个线程池,其实现与原来使用的公共线程池基本相同,只是核心线程数、最大线程数、等待队列这3个参数根据服务器配置和Druid 数据库连接池配置进行了调整。

    因为该任务是一个定时任务,只是在每天的一个固定时间执行,大部分时间核心线程处于闲置状态,所以核心线程数过大会消耗不必要的资源,因此 CORE_POOL_SIZE 设置成5;

    当定时任务开始执行时会有大量的数据查询任务被丢进线程池,所以最大线程数可以设置的稍大些但一定不能超过数据库连接池内的连接数(避免相同情况下继续死锁),同时也要给该微服务的其它模块留数据库操作的余量,因此MAX_POOL_SIZE 设置成数据库连接池的一半大小。

    因为执行任务所反问的数据表数据量大概在 4000万 这个级别,使用线程池进行并发执行,每个线程批量插入时的 BATCH_SIZE5000,为保证整个任务执行过程不丢失数据,于是将任务队列的大小设置成 QUEUE_CAPACITY = 10000

    CORE_POOL_SIZE = 5
    MAX_POOL_SIZE = 20
    QUEUE_CAPACITY = 10000
    

3. 总结
  • 根本原因

    定时任务使用连接池线程数设置过大,导致定时任务执行时,数据库连接池资源耗尽,数据库锁竞争造成死锁导致大量请求被阻塞。

  • 解决方案
    kill 掉阻塞进程优先恢复生产,定位到服务中的死锁代码后,通过修改配置和服务代码的实现来彻底解决问题。

    • 优化连接池配置,增加连接数并检测连接泄漏;
    • 调整目标任务使用线程池的配置,避免其将数据库连接池资源耗尽,并给该服务其它模块数据库连接留余量;

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

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

相关文章

【计算机网络应用层】

文章目录 计算机网络应用层详解一、前言二、应用层的功能三、常见的应用层协议1. HTTP/HTTPS&#xff08;超文本传输协议&#xff09;2. DNS&#xff08;域名系统&#xff09;3. FTP&#xff08;文件传输协议&#xff09;4. SMTP/POP3/IMAP&#xff08;电子邮件协议&#xff09…

Linux 虚拟化方案

一、Linux 虚拟化技术分类 1. 全虚拟化 (Full Virtualization) 特点&#xff1a;Guest OS 无需修改&#xff0c;完全模拟硬件 代表技术&#xff1a; KVM (Kernel-based Virtual Machine)&#xff1a;主流方案&#xff0c;集成到 Linux 内核 QEMU&#xff1a;硬件模拟器&…

树莓派 5 换清华源

首先备份原设置 cp /etc/apt/sources.list ~/sources.list.bak cp /etc/apt/sources.list.d/raspi.list ~/raspi.list.bak修改配置 /etc/apt/sources.list 文件替换内容如下&#xff08;原内容删除&#xff09; deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm …

WGAN原理及实现(pytorch版)

WGAN原理及实现 一、WGAN原理1.1 原始GAN的缺陷1.2 Wasserstein距离的引入1.3 Kantorovich-Rubinstein对偶1.4 WGAN的优化目标1.4 数学推导步骤1.5 权重裁剪 vs 梯度惩罚1.6 优势1.7 总结 二、WGAN实现2.1 导包2.2 数据加载和处理2.3 构建生成器2.4 构建判别器2.5 训练和保存模…

Unity网络开发基础 (3) Socket入门 TCP同步连接 与 简单封装练习

本文章不作任何商业用途 仅作学习与交流 教程来自Unity唐老狮 关于练习题部分是我观看教程之后自己实现 所以和老师写法可能不太一样 唐老师说掌握其基本思路即可,因为前端程序一般不需要去写后端逻辑 1.认识Socket的重要API Socket是什么 Socket&#xff08;套接字&#xff0…

【linux】一文掌握 ssh和scp 指令的详细用法(ssh和scp 备忘速查)

文章目录 入门连接执行SCP配置位置SCP 选项配置示例ProxyJumpssh-copy-id SSH keygenssh-keygen产生钥匙类型known_hosts密钥格式 此快速参考备忘单提供了使用 SSH 的各种方法。 参考&#xff1a; OpenSSH 配置文件示例 (cyberciti.biz)ssh_config (linux.die.net) 入门 连…

真实笔试题

文章目录 线程题树的深度遍历 线程题 实现一个类支持100个线程同时向一个银行账户中存入一元钱.需通过同步机制消除竞态条件,当所有线程执行完成后,账户余额必须精确等于100元 package com.itheima.thread;public class ShowMeBug {private double balance; // 账户余额priva…

2.2 路径问题专题:LeetCode 63. 不同路径 II

动态规划解决LeetCode 63题&#xff1a;不同路径 II&#xff08;含障碍物&#xff09; 1. 题目链接 LeetCode 63. 不同路径 II 2. 题目描述 一个机器人位于 m x n 网格的左上角&#xff0c;每次只能向右或向下移动一步。网格中可能存在障碍物&#xff08;标记为 1&#xff…

2874. 有序三元组中的最大值 II

给你一个下标从 0 开始的整数数组 。nums 请你从所有满足 的下标三元组 中&#xff0c;找出并返回下标三元组的最大值。 如果所有满足条件的三元组的值都是负数&#xff0c;则返回 。i < j < k(i, j, k)0 下标三元组 的值等于 。(i, j, k)(nums[i] - nums[j]) * nums[k…

【论文笔记】Llama 3 技术报告

Llama 3中的顶级模型是一个拥有4050亿参数的密集Transformer模型&#xff0c;并且它的上下文窗口长度可以达到128,000个tokens。这意味着它能够处理非常长的文本&#xff0c;记住和理解更多的信息。Llama 3.1的论文长达92页&#xff0c;详细描述了模型的开发阶段、优化策略、模…

JVM深入原理(一+二):JVM概述和JVM功能

目录 1. JVM概述 1.1. Java程序结构 1.2. JVM作用 1.3. JVM规范和实现 2. JVM功能 2.1. 功能-编译和运行 2.2. 功能-内存管理 2.3. 功能-即时编译 1. JVM概述 1.1. Java程序结构 1.2. JVM作用 JVM全称是Java Virtual Machine-Java虚拟机 JVM作用:本质上是一个运行在…

SQL Server Integration Services (SSIS) 服务无法启动

问题现象&#xff1a; 安装 SQL Server 2022 后&#xff0c;SQL Server Integration Services (SSIS) 服务无法启动&#xff0c;日志报错 “服务无法响应控制请求”&#xff08;错误代码 1067&#xff09;或 “依赖服务不存在或已标记为删除”。 快速诊断 检查服务状态与依赖项…

Spring Boot 定时任务的多种实现方式

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Java基础之反射的基本使用

简介 在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意属性和方法&#xff1b;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。反射让Java成为了一门动…

AI产品的上层建筑:提示词工程、RAG与Agent

上节课我们拆解了 AI 产品的基础设施建设&#xff0c;这节课我们聊聊上层建筑。这部分是产品经理日常工作的重头戏&#xff0c;包含提示词、RAG 和 Agent 构建。 用 AI 客服产品举例&#xff0c;这三者的作用是这样的&#xff1a; 提示词能让客服很有礼貌。比如它会说&#x…

蓝桥杯刷题记录【并查集001】(2024)

主要内容&#xff1a;并查集 并查集 并查集的题目感觉大部分都是模板题&#xff0c;上板子&#xff01;&#xff01; class UnionFind:def __init__(self, n):self.pa list(range(n))self.size [1]*n self.cnt ndef find(self, x):if self.pa[x] ! x:self.pa[x] self.fi…

海外SD-WAN专线网络部署成本分析

作为支撑企业国际业务的重要基石&#xff0c;海外SD-WAN专线以其独特的成本优势和技术特性&#xff0c;正成为企业构建高效稳定的全球网络架构的首选方案。本文将从多维度解构海外SD-WAN专线部署的核心成本要素&#xff0c;为企业的全球化网络布局提供战略参考。 一、基础资源投…

操作系统(二):实时系统介绍与实例分析

目录 一.概念 1.1 分类 1.2 主要指标 二.实现原理 三.主流实时系统对比 一.概念 实时系统&#xff08;Real-Time System, RTS&#xff09;是一类以时间确定性为核心目标的计算机系统&#xff0c;其设计需确保在严格的时间约束内完成任务响应。 1.1 分类 根据时间约束的严…

Golang的消息中间件选型

# Golang的消息中间件选型 消息中间件的作用 消息中间件是一种用于分布式系统中应用程序之间进行通信的基础架构工具&#xff0c;它能够有效地解耦发送者和接收者&#xff0c;并提供高可用性和可靠性的消息传递机制。在Golang应用程序中&#xff0c;选择适合的消息中间件对于构…

大模型中的参数规模与显卡匹配

在大模型训练和推理中&#xff0c;显卡&#xff08;GPU/TPU&#xff09;的选择与模型参数量紧密相关&#xff0c;需综合考虑显存、计算能力和成本。以下是不同规模模型与硬件的匹配关系及优化策略&#xff1a; 一、参数规模与显卡匹配参考表 模型参数量训练阶段推荐显卡推理阶…