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

云掣YunChe2个月前最新动态104


在数字化时代,数据是企业的核心资产,而数据库则是存储和管理这些资产的重要仓库。本文通过云掣为某信息化管理行业客户快速解决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的线程,最终导致数据库线程池的泄露。在此次故障中,云掣迅速响应了客户的需求,快速定位问题根源,尽可能的实现了业务停机时间的最小化,成功保障了数据库的安全。

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


相关文章

初识MySQL数据库

初识MySQL数据库

数据库首先提问,按照常理来说,什么是数据库呢?不少人想到的一点就是,不就是数据的集合吗?是的,数据库从数据存储方面确实只是数据的集合,但是数据库不只是为了集合数据而存在。如果是为了集合数据,像我们磁盘...

指标+AI+BI,袋鼠云构建智能数据分析新范式

指标+AI+BI,袋鼠云构建智能数据分析新范式

10月30日,袋鼠云成功举办了以“AI驱动,数智未来”为主题的2024年秋季发布会。大会深度探讨了如何凭借 AI 实现新的飞跃,重塑企业的经营管理方式,加速数智化进程。作为大会的重要环节之一,袋鼠云数...

大数据基础之Hive入门介绍

大数据基础之Hive入门介绍

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

现场太火爆,CEO徐进挺带来Oracle上云最佳实践

现场太火爆,CEO徐进挺带来Oracle上云最佳实践

10月20号,云栖大会“企业上云与数据管理专场论坛”上,袋鼠云CEO徐进挺(丁原)以《阿里云&袋鼠云oracle上云最佳实践》为主题发表演讲,以Oracle单节点、Oracle DG、Orac...

袋鼠云港口数智化解决方案发布,数智引领,加速“智变”

袋鼠云港口数智化解决方案发布,数智引领,加速“智变”

2023年12月,交通运输部印发《关于加快智慧港口和智慧航道建设的意见》,《意见》贯穿了“3条主线”,其中最首要的主线是“数字化”,数字化是基础,必须通过数字赋能建设、生产、运营、管理、服务的全要素、...

可观测领域系列之存储分析鉴赏Talk(Part 1)

可观测领域系列之存储分析鉴赏Talk(Part 1)

前面有文章也提到过,可观测领域目前尚存的一些短板,其中存储分析就是其一,只能根据过往的一些经验,以及调研分析来尝试帮大家鉴赏下当前这个领域的一些技术和竞品分析。在分析这个问题之前,我们先从场景出发,以...

发表评论    

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