说正事儿了,技术社群的这篇文章《用户故事 | 工商银行核心应用 MySQL 治理实践》是工行的林老师,在2020年技术大会上的一次分享,当时我在现场听的这个主题,例如SQL审核等很多内容,还是引起共鸣的,非常值得借鉴,碰巧看到了这篇文章,引入于此,各位可以结合实际的工作,细细品味,借助这些经验,考虑一些可以融合的方向和可执行的点。
工商银行在核心应用 MySQL 的治理,主要分为三个方面:目前面临的情况和挑战、为了解决这些问题的思路和具体的方案,后续提升的思路。
一、 现状与挑战
1.1、现状
这是行内 MySQL 的部署节点数发展情况,从 2019 年 6 月开始到现在,在短短的两年期间,MySQL 节点规模上涨得非常厉害,翻了几番。绿色部分是目前这些节点当中的核心应用节点数的情况。核心应用占的比例其实是比较大的,有两个方面的因素:一方面是行内在数据库使用的策略上,对于新增的数据库节点,如果没有特别要求的话都会使用 MySQL 数据库;另一方面是与行业内业务发展情况相关,行内对于高容量、高并发、弹性扩展的业务需求非常旺盛,这种情况下对于分布式架构的数据库要求越来越多,所以对于分布式内容的应用规模也会比较庞大。
行内将应用分为 ABCD 四个等级,最高等级为 A 类应用,核心应用就是指最高等级的 A 类应用。
1.2、面对的挑战
行内对高容量、高并发、弹性扩展的业务需求比较多。目前基本完成了分布式体系的建设,满足业务的需求,MySQL 也是分布式数据库体系的一部分,除了数据库之外也实现了分布式服务、软负载、分布式事务、分布式消息、批量、缓存、对象存储、文件存储,加上数据库等,共有九大运行支撑平台。通过这些平台的组合,形成了完整的分布式解决方案,满足业务的要求。
关于运维方面的压力,现在 MySQL 节点数量非常庞大,对于生产运维来说,为了能够支撑这么大的体量,运维的压力非常大,包括监控告警、故障恢复等。如果没有借助自动化、智能化的手段是很难满足运维的要求。这里我们是与第三方公司一起合作研发 MySQL 的管理平台,具体的是爱可生公司。我们也经历了很长的一段合作时间,能够把这套东西打磨出来,基本覆盖了完整的运维流程,特别是对故障的诊断和自动化的切换,效果也是非常好的。在这么多节点的大基数情况下,发生故障的总体概率会相对较高,但都可以很好地应对,在故障时迅速切换,基本不会对业务造成影响。
现在这么大的体量,如果按照传统物理机的部署模式,资源浪费会比较大,特别是在 CPU 方面。因为 MySQL 正常使用的话,CPU 资源一般都会比较低。这个问题行内看得比较远,资源规划方面已经实现了 90% 的容器化部署,目前大部分 MySQL 已经运行在容器上面,效果也非常明显,使用率能够提升 4-5 倍的程度。
本次我想重点介绍的内容是,现在核心应用接入到 MySQL,怎样才能保证生产运行的过程中,降低问题的数量和影响的程度?可以分为几个方面来讲:事前,我们希望能够在研发阶段尽快发现一些问题;事中,降低问题的影响;事后,希望快速定位到问题,迅速地予以解决。
二、 治理思路与方案
这些就是对治理思路的总结。首先是基础规范的管理工作。对于我们所需要做的工作,所有要求都是应该落在规范上面,希望做到有理有据,对于后面问题的定位和责任的划分都会非常有帮助。在此基础上,我们进行三个方面的治理工作,包括事前、事中和事后。
2.1、规范起到的三个作用
首先规范能够制定操作的标准,例如建表的时候该怎样,每张表必须要有主键,建库建表甚至设计字段的时候不允许在 SQL 当中添加字符集和排序规则的属性,默认实例的属性就可以。
其次规范可做量化的控制,有时要求一条 SQL 扫描的数量与返回的数量都不能太多,执行时间不能太长等等,从定性角度看问题都不大,但要落到实处,这样的要求是不够的,因此需要提供量化的指标。比如对慢 SQL,要求联机交易扫描的行数和结果集行数比不能超过 100:1。对于一个事务,更新的数量不能超过 10 万条。指标在开始设计时可能并不完美,但在使用的过程中不断研究、完善,就能够定出来更细化的指标。对于开发或是运维,都要对这些程序的设计和运行有一个底线的意识,一定不能越过这条线。
再是为了避开一些 Bug。一个例子是,大表的 Truncate 会导致数据库 hang 住,昨天农行的同事也有讲到,大家遇到的问题基本都是一样的。另外一个例子是 Replace into 的 Bug,会导致主备的元数据信息不一致,如果发生切换,新的主库插入数据的时候会出现主键冲突,这个问题相对比较大。为避免此类问题,规范上会要求不允许使用。
2.2、具体规范内容有两个特点,供大家参考
规范应该是容易被理解的,所以在规范中特意针对每一个条款增加了一个解读的内容。虽然规范提出了一些要求,但在具体落实时还会有很多人并不知道为什么会有这样的要求。所以在文档当中增加了很多解释,可以让读者很清晰地知道,除了不能这么做之外,还能知道为什么不能这么做。
对于规范当中的内容,会尽量安排落地,例如事前的治理方面,涉及到表结构和代码的审核系统,都是基于规范的内容安排具体的落地。所以规范才会真正变成非常有效的工具,不会是一纸空文。
2.3、我们三个主要工作,事前预防,事中应急,事后诊断
首先是事前预防工作
表结构的审核,是规范要求的落地实现,包括每个表必须建立主键,禁止单独设立字符排序规则,或者使用 TimeStamp 的数据类型等。行内目前的表结构审核系统,除了进行审核之外还负责进行版本控制,主要有两种情况:对于新增表,建表的语句是由审核系统生成的,不需要人工来写代码,可以减少很多不必要的错误。这一点我也是深有体会。之前看到不少同事的表结构是从一个地方拷贝过来,有些属性不是很理解,特别是表的属性中存在存储引擎的属性、排序规则的属性,不知道的话就自己带进来了,里面的要求可能都不是我们想要的。现在我们都是统一使用 InnoDB 引擎,要是不小心的话就会把 MyISAM 的表带入进来,所以隐患还是比较大的。表结构变更,例如加一个字段或加一个索引,这也是由表结构审核系统生成,不需要人工实现。
代码审核方面,我们在 MySQL 审核基本上都是基于 MyBatis 的开发方式,包括配置文件的扫描和实现,下面举的例子就是代码审核的要点。现在也是在持续地补充和完善。
健康检查方面,可以举个例子,是关于慢 SQL。这也是大部分公司在 MySQL 遇到最多的一类问题。我们利用 MySQL 中的一张视图进行分析,作为检查的一个依据。这张视图中记录了每一条 SQL 从 MySQL 开机到现在运行的计数器,比如截止到目前运行的次数,花费的时间,扫描记录的条数。我们利用这张表的计数器的特点,在两个时间点采集相同的表的数据,并且将两个时间点的数据进行比较,可得到这段时间内这些 SQL 运行的指标。对于相同的语句在八点钟采集一次,在九点钟采集一次,之后两个数据相减就知道 SQL 在八点钟到九点钟一共执行 100 次,执行时间是 900 秒,扫描记录数是 9000 万。从这个数字来看,有些比较敏感的同事,会发现单条记录的执行时间应该是偏长的,并且单条记录扫描的记录数也明显偏多。那么这条 SQL 就有一个明显的效率问题,需要进行整改。大部分问题都是可以通过增加索引来解决。
事中应急
事中主要考虑应急,并且是自动化应急。接触过生产的同事对应急应该是深有感触,要遇到一个问题之后开始分析、定位问题,最后采取措施,中间这段时间可能是相对不可控的,有时很快能够处理,但也有需要几十分钟甚至几个小时才能处理完毕,这时已经对业务造成了非常大的影响。因此我们更多的要考虑,怎样实现这种自动化的应急手段。
关于监控查杀,我们希望能够通过监控实时地捕捉到性能上的问题,进行自动查杀的操作,也就是把对应的线程杀掉,避免问题的进一步扩大。刚才多次提到的慢 SQL,是我们面临的最大敌人,主要包括两个方面的危害:很多时候,有大量的数据扫描,导致吃 CPU 资源。在大并发情况下,一条 SQL 可以吃掉一个 CPU,几十个 SQL 就能够把服务器的 CPU 吃光,问题的影响也是非常大。SQL执行慢,并且有多个线程堆积的情况下,可以把 InnoDB 的线程池耗尽。在这种情况下,原来一些没有问题的 SQL,在执行的时候就会明显被拖慢,导致整个系统交易全部受到影响。所以就不仅仅是慢 SQL 的交易,整个系统的交易都会存在问题,影响很大。
我们对此已经实现联机交易的自动查杀功能,通过设置一个阈值,对于超过这个阈值的 SQL 自动杀掉。具体原理比较简单,通过类似 ProcessList 得到时间点和线程 ID,然后去查杀。在这里有个特别要注意的地方,为了实现这些,我们做了联机和批量用户分离操作,针对不同用户进行差异性处理。主要是因为联机和批量的特性不一样,批量的 SQL 有时会要做大批量的处理,时间是会比较长,这是很正常的。联机基本上是短平快的操作,大部分都是毫秒级的,稍微慢一点的可能是几秒钟,但十几秒、几十秒的话就是不正常了。所以是在用户分离的前提下再进一步做慢 SQL 的查杀。
关于大事务,我们的定义是在一个事务当中增删改的记录数超过一定的阈值。目前定义的阈值是 10 万条记录,超过 10 万条就定义为一个大事务。具体危害性可以从这张图进行介绍。每笔交易都会经历一个记录 binlog 再从备库返回的过程,这是 MySQL 半同步最基本的操作。如果记录的内容比较大,那么具体的量也随之非常大,不管是写入、传输还是落地方面时间都会有明显差异。目前我们觉得最大的问题在于,MySQL 主库写入 binlog 的处理都是单线程的,如果有一个交易写入,其它交易都是排队的状态。如果出现大事务的话,其它交易就会被卡住。
在主库出现堵塞的情况下,高可用的机制可以探测到主库长时间不可用,我们就会去做主备的切换。但是在大事务的情况下,切还是不切是比较大的问题。大事务的写入、传输和回放需要经历一个比较漫长的阶段,如果马上用备库的话,数据就已经不是最新的,也就是会丢掉一些数据;如果我们等待回放,做完以后再切,可能时间就不可接受了。所以这是一个两难的问题,我们要尽量避免大事务。
为了做到严格的控制,我们也对大事务实现了自动查杀,对于超过一定的阈值自动执行和操作。
自动查杀也不是立即能够实施,需要经历不断的小范围的试点,先做监控报警,再做自动查杀,这样一步一步走下来。
有候我们开玩笑,谈到删库跑路,例如遇到程序 Bug 有些数据会被误删,或者更新成其它我们不需要的数据,这时候就需要进行数据恢复。常规的办法是存量备份和增量 BinLog 进行追补。可追补的过程中是单线程的,基本上很难实施,因为存量备份都是每周一次,要是恢复的数据是五六天之前,追补的数据范围会非常大。我们采用两种方案:伪装成 Slave 进行回放,网上已经有现成的案例;为了更快地进行恢复,我们借鉴了一些业界恢复工具,主要包括两类,一类是 DML,比如 Delete 转化为 Insert,另一类是基于文件系统工具进行恢复。
然后是事后诊断
最重要还是数据的采集能够完整到位,对事后诊断才是有帮助的。除了常规的数据采集,针对 MySQL,还有些高密度和低密度的采集。高密度是针对后台线程情况的采集,是为了解决一些瞬间的性能波动,有时只是一两分钟或者更短的十几秒、几十秒的波动,通过高密度的采集能够作为事后分析的依据。低密度是刚才提到的慢 SQL 的性能数据,目前是十五分钟采集一次。SQL 的历史执行情况对问题分析也十分有帮助,也是十五分钟采集一次。
三、 后续提升思路
3.1、首先是问题的定位,有两方面
在问题发生之前,怎样在研发测试环境,甚至生长环境能够提前捕捉到;问题发生的时候怎样能够迅速地定位到问题,迅速采取一些措施解决。前面有提到检查和自动化的处理,但还是远远不够,还是有很多的提升空间。
3.2、其次是问题的预判
根据一些性能数据,包括性能指标和 SQL 的执行情况、发展曲线提前进行预判。比如有些表刚开始的数据量不是很大,即使有些 SQL 效率不高,执行的情况还是可以接受的。但随着数据量的增长,执行时间和扫描数据量都在不断攀升,在达到一个临界值影响到具体业务之前,我们希望能够找到它,并且尽快修正过来。
3.3、最后是关于问题的自愈
现在希望借助于智能自动化的手段,能够在出现问题的时候自动地做修复,避免这些问题进一步扩大。例如,有些程序写得不够好,就会出现全表扫描的情况,如果是在出现问题后再去定位添加索引,时效性会比较慢。希望我们的程序能够自动发现这个问题,然后加上一个索引,可以让这个业务基本上不受到影响。
如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"