MySQL数据库线程池泄露场景解决方案

云掣YunChe8个月前最新动态578


在数字化时代,数据是企业的核心资产,而数据库则是存储和管理这些资产的重要仓库。本文通过云掣为某信息化管理行业客户快速解决MySQL数据库故障的案例,详细的分享了MySQL数据库出现问题时的解决思路和具体方案。以帮助广大DBA快速定位、解决MySQL数据库线程池泄露的问题,希望能为大家提供一些借鉴和启示。

方案背景

首次产生故障

2022年12月1号上午10点左右,云掣收到了阿里云的监控告警,某信息化管理行业客户的官网探测到了异常,如图所示:

image (12).png


我们的DBA立刻查看了客户的后端数据库实例,发现数据库连接被用尽,导致服务出现了异常。

image (8).png

和客户协商后决定对数据库会话进行kill处理。

image.png

经过临时处理后,应用恢复正常。

image (1).png

再次出现故障

当天下午14:16,客户再次联系我们,反馈部分服务接口超时,如图所示:

image (2).png

由于客户的服务底层代码为Go,我们曾建议在代码中加入pprof进行debug,否则出现问题时我们无法查看底层的线程池和堆栈信息,但客户尚未将pprof加入代码。

在这种情况下,我们尝试通过tcpdump抓取网络包,试图分析go应用到底在干什么。通过wireshark解包后,我们发现了异常情况:go应用和MySQL数据库之间进行了大量的TCP Keep-Alive网络包交互,但是没有正常的SQL查询交互。

image-20221209164205966.png

初步判断为go应用存在数据库连接池泄露,导致应用的数据库连接池用尽,最终出现如上图的情况,只有TCP Keep-Alive,没有正常的交互SQL。但是因为没有pprof,再加上处于业务高峰期,我们无法快速定位到泄露代码,客户选择临时重启应用进行修复。

解决方案

问题复现

加入pprof之后,我们跟踪了goroutine,但根据跟踪的图,还是无法定位到故障代码。

image (3).png

在这种情况下,问题似乎走入了死胡同。

但仔细回想,之前网络抓包的时候,应用已经处于假死状态,所以我们无法看到到底是因为什么SQL导致的。如果重启应用,在服务刚开始的时候就进行网络抓包,应该就可以抓到问题SQL。

我们再次进行抓包,这次发现了异常SQL:每个连接数据库的TCP连接,最终退出的时候,都执行了开启事务,如下图所示:

image (4).png

最终定位到问题代码为以下代码:

image (5).png

最终解决

因为客户想深入排查为什么会出现此bug,所以我们再次分析了网络包,发现出现问题的TCP线程池为101个。而我们在pprof跟踪的图中看到对应数量的goroutine: runtime goparkunlock,这个goroutine的最上级是 sql(*Tx)awaitDone,如图所示:

image (6).png

使用谷歌搜索:sql(*Tx)awaitDone,发现很多人都遇到了相似问题,点赞最高的回答如下⬇️:

您要确保Begin()、Commit()和Rollback()出现在同一个函数中。它使交易更容易跟踪,并让您确保它们通过使用defer.

这是一个这样的例子,它根据是否返回错误来执行 Commit 或 Rollback:

func (s Service) DoSomething() (err error) {
    tx, err := s.db.Begin()
    if err != nil {
        return
    }
    defer func() {
        if err != nil {
            tx.Rollback()
            return
        }
        err = tx.Commit()
    }()
    if _, err = tx.Exec(...); err != nil {
        return
    }
    if _, err = tx.Exec(...); err != nil {
        return
    }
    // ...
    return
}


这可能会有点重复,另一种方法是使用事务处理程序包装事务:

func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (err error) {
    tx, err := db.Begin()
    if err != nil {
        return
    }
    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p) // re-throw panic after Rollback
        } else if err != nil {
            tx.Rollback() // err is non-nil; don't change it
        } else {
            err = tx.Commit() // err is nil; if Commit returns error update err
        }
    }()
    err = txFunc(tx)
    return err
}


使用上面的事务处理程序,可以这样做:

func (s Service) DoSomething() error {
    return Transact(s.db, func (tx *sql.Tx) error {
        if _, err := tx.Exec(...); err != nil {
            return err
        }
        if _, err := tx.Exec(...); err != nil {
            return err
        }
        return nil
    })
}


最终客户采用了以上所说的stackoverflow.com高赞回答,成功解决了bug并通过测试,如下图所示:

image-20221209180321353.png

image (7).png

总结

本次BUG根源在于线程中开启了事务,但是在线程出现panic的时候,未做事务回滚处理。由于gin框架会自动recover发生panic的线程,最终导致数据库线程池的泄露。在此次故障中,云掣迅速响应了客户的需求,快速定位问题根源,尽可能的实现了业务停机时间的最小化,成功保障了数据库的安全。

展望未来,随着技术发展和业务变化,数据库运维将面临更多挑战。云掣也会持续不断地进步、创新,完善优化运维策略,应对各种挑战,为客户的数据资产提供更高效、可靠的保障。


相关文章

大数据基础之Hive入门介绍

大数据基础之Hive入门介绍

一、什么是HiveHive 是建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具,可以用来进行数据提取转化加载(ETL ),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数...

ACOS统一监控之自动化巡检

ACOS统一监控之自动化巡检

作者:晓风引言随着信息技术的发展和普及,企业的 IT 系统已经成为企业运营的重要组成部分。在这些系统中,应用程序和服务的可用性和性能对业务运营至关重要。然而,IT 系统的复杂性和规模,以及 IT 运维...

正式发布 | 《云运维服务白皮书》开放下载!

正式发布 | 《云运维服务白皮书》开放下载!

在全球数字化变革的背景下,为适应数字经济环境下企业生存发展和市场变化的需要,企业进行主动的、系统性、整体性的数字化转型升级。大数据、云计算、人工智能、区块链等新一代信息通信技术为企业的数字化转型提供了...

袋鼠云秋季发布会圆满落幕,AI驱动让生产力数智化

袋鼠云秋季发布会圆满落幕,AI驱动让生产力数智化

在当今时代,AI 的发展如汹涌浪潮,其速度之快超越了任何历史时期。它以前所未有的迅猛之势,渗入到各个领域的不同场景之中,悄然重塑着商业模式与人们的生活方式。在 AI 逐渐成为企业基础属性的背景下,袋鼠...

深度解读|云掣《云运维服务白皮书》全方位解析!

深度解读|云掣《云运维服务白皮书》全方位解析!

如今全球各行各业纷纷进行数字化变革,为适应数字经济环境下企业生存发展和市场变化的需要,企业选择进行主动的、系统性、整体性的数字化转型升级。大数据、云计算、人工智能、区块链等新一代信息通信技术为企业的数...

ACOS可观测运维套件—容器监控

ACOS可观测运维套件—容器监控

一、简介ACOS统一运维监控平台最新4.0版本支持容器能力,对于容器数据采集成功后会上报到控制台「容器监控」,您可以查看到租户内容器各种对象的数据信息。容器数据有两种查看和分析模式,通过切换页面左上角...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。