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

云掣YunChe11个月前最新动态1031


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

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


相关文章

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

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

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

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

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

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

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

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

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

如何将现有 MySQL 数据库平滑升级至 8.0 版本?

如何将现有 MySQL 数据库平滑升级至 8.0 版本?

近几年在信创趋势下,国产数据库和云 RDS 多基于开源 MySQL。随着 MySQL 8.0 版本逐渐受到大家认可,很多企业都表示要升级 MySQL 版本,更好地利用新版本中的高级特性和性能改进。随着...

高效安全迁移:PG高可用集群实战方案深度解析

高效安全迁移:PG高可用集群实战方案深度解析

PostgreSQL是一个开源的数据库管理系统,相比于其他开源数据库系统,PostgreSQL有更加丰富的数据类型和可扩展性,并因此被广泛采用。在实际工作中,若企业业务需求变动,则有可能面临PG高可用...

阿里云再度携手袋鼠云,重磅联合发布ACOS统一运维监控平台

阿里云再度携手袋鼠云,重磅联合发布ACOS统一运维监控平台

1月13日,袋鼠云旗下数据化运维子公司云掣科技与阿里云再度签署战略合作协议,阿里云智能云原生应用平台负责人丁宇(花名:叔同)、阿里云云原生PaaS负责人张军(花名:游骥)、阿里云智能云原生可观测负责人...

发表评论    

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