运维不背锅!两年来优化数据库零故障运维的方式也从简单的Oracle数据库转向了各种数据库的运维。目前管理着各类数据库实例超过10000个。在这种情况下,我们已经连续两年保持了数据库零故障的状态。其实在过去,运维团队也是每天忙于处理各种异常,长期处于高压状态。经过我们团队的一系列优化和改造,系统现在更加稳定。这期间确实发生了很多事情。下面简单介绍一下我们这两年对一些运维问题和团队管理方法的分析,以期对大家有所启发。一、解题首先,我们先从下图说起:这是扁鹊向魏王介绍他们三兄弟的医术:扁鹊的二哥是治人小病;而扁鹊的大哥则是防患于未然,远离疾病。在扁鹊看来,三人医术的顺序应该是:大哥>二哥>扁鹊,但在世人眼里却是:扁鹊>二哥>大哥。我很赞同扁鹊的观点,因为我一直认为DB的运维人员不应该一味的指责,而应该把自己当成医生对待问题。他们不仅要注意解决问题,更需要注意避免问题。发生。当我们解决数据库异常的问题时,别人可能会认为我们是能解决问题、能处理事情的专家。但实际上,此时的运维已经是一个被动的过程。即使用最快的方法解决,故障已经发生,可能造成比较严重的影响。如果我们能够提前发现这些问题并加以解决,就可以避免很多失败和影响。所以,在我看来,比较巧妙的运维方法应该是:解决在做架构或者设计的时候能够提前想到的问题,保证系统的可扩展性和高可用。运维也要从架构的角度考虑问题,提前解决这些问题,而不是被动地等问题出现了再去解决。下面介绍一下我们团队解决的三个案例,以及之后我们是如何规避问题的:案例1这是我们在2016年解决的一个案例,版本是Oracle数据库11.2.0.4版本。本数据库使用SPMSolidifyTOPSQL的执行计划来保证系统的稳定性。exception当时的问题是,几乎每发布一个大版本,现有的功能都会受到一定的影响,某些语句的执行计划会出现异常。我们发现大部分语句都和这个语句类似,中间有一个SKIPSCAN(跳过扫描),很明显是输入时间(面向用户),还有一个范围查询。所以我们基本判断问题就出在这里,我们采用的解决方案是通过重新固化执行计划来选择一个好的执行计划。然后开始分析问题的原因。对于索引跳过扫描,一般情况下,如果运维索引的第一列没有被使用,当开始使用第二列时,只能使用skip方式进行一次索引扫描。在分析问题原因的时候,因为相似的句子很多,我们发现固化了几十个之后,相似的句子还是源源不断,所以我们认为问题可能没有那么简单。于是我们进一步分析,最终发现可能是该指标的统计信息有问题。所以我们重新收集了索引的统计信息,至此,类似的句子问题已经解决了。但其实这个问题并没有完全结束:我们处理完后重新分析了索引问题,发现索引***列是空值,但是不知道是谁在空列和空列之间建立了匹配输入时间索引,导致这个索引可能被使用。发现问题后,我们检查了这个索引的访问方式,看是否都是INDEXSKIPSCAN。后来发现基本上访问这个索引的语句都是用这个索引skipscan,所以我们当时设置这个索引不可用,然后删除。我们当时处理了三个类似的指标,之后系统就没有出现过类似的问题了。SPM也是一种固化执行计划的方式,但是为什么SPM在这个库中会失败呢?然后我们分析了原因:是因为每发布一个版本,可能会多检查一些字段,导致语句发生变化。SPM等固化执行计划的方法与语句有很强的相关性。只要语句稍有改动,固定的方法就会失效。这也是每次更新版本语句都会出现异常的原因之一。那么我们继续分析,为什么这个时间指数会出现这样类似的问题呢?这是我们之前整理过的一个案例分析的原因:当我们做索引范围查询的时候,它的选择性公式如下,但是在不同的情况下,比如这个右手边的索引,比如创建时间,更新时间,输入时间,我们写的数据是写在sysdate里面的,所以它会一直在索引列的右边,数据插入的方式也是类似的。但是,我们收集的统计信息并不是实时收集的。主要是针对一些大表。比如1000万的表可能需要10%的DMR量,也就是100万;较大表的DMR数量会更高。大的。这样就会导致我们的统计数据和当前值永远过时,就会出现这种问题。对于这三个查询:第一个查询发生在有效范围内,所以可以反映一个比较真实的数据,第二个查询也可以反映一部分,但是第三个查询相当于完成了一个超范围查询,计算出一个很低的值,就会导致我们的语句出现异常。更糟糕的是,在OLPP系统中,查询新数据的概率总是大于查询旧数据的概率,越新的数据越容易被访问到,这也导致我们的语句每次都会出现异常情况。发现这些问题后,我们立即展开行动,就是把数据库中所有与时间索引相关的字段都提取出来,然后定期修改索引字段上的HIGHVALUE和统计信息中的HIGHVALUE来避免这个问题..如上图,是范围查询的情况,也就是索引前导列的差异,类似于我们建索引的创建时间和OWNER。如果把创建时间放在前面,把OWNER放在后面是第一种情况;如果把OWNER放在前面,把CREATED放在后面就是第二种情况。下面我们来分析一下这两个不同的指标的区别:当我们把创建时间放在第一位的时候,问题就大了。我们通过时间字段查询时,很难做到等值查询,即无法找到每秒插入的值。对于这种查询,我们一般使用范围查询,比如查询一个月或者一天或者一周的数据。所以你可以看到,如果我在这个语句中查看DBMGR在这一分钟和一天中创建的情况,它的整个范围都会涉及到第一个索引,然后取三个关联值;但是在第一个索引中,两个索引中,三个值是连在一起的,因为DBMGR是有序的,时间也是有序的,它们可以完成只涉及到自己相关值的取值。还有一些小区别:如果我先进行范围查询,再进行等价查询,对于这样的索引,在过滤的时候会多执行一个Filter步骤。但如果调整了这个顺序,就不是这样了。所以,我们在创建这种匹配索引的时候,一定要尽量把等价查询放在前面。之前有个说法:在选择符合指标的前导列时,应该将选择率比较低的值放在前导列中。但是我们认为这个说法是不完整的:比如对于一个时间字段,一天有86400秒,100天可能有超过800万个不同的值,一年有更多不同的值。但是如果用这个作为前导列,有时候就不合适了,因为对于它,我们可能需要查询一天或者一个月的数据,而一年有365天,也就是十二个月。因此,更准确的说法是将查询条件中选择率低的列作为复合索引的前导列。所以通过这个案例,我们把运维问题的解决分为三个步骤:第一步:快速解决问题,保证应用恢复。对于运维人员来说,恢复应用是第一步;第二步:检查问题是否反复出现。比如上面提到的案例1,如果我们当时的解决方案只是固化执行计划或者收集统计信息,你没有办法保证以后不会再出现类似的情况。如果我们采用收集统计信息的方式,这种情况可能再过一两个月就会再次发生,所以根本的解决办法就是找到这个问题的原因,并保证这个问题解决后不会再发生。复发;第三步:回避问题,看这个问题是不是通病,其他库有没有类似的问题。如果有类似的问题,就要形成一个规范,避免此类问题的发生。尤其是一些新的应用,只有你制定规范,让开发合规,才会减少后续类似问题的发生,否则就会演变成我们解决问题,新问题不断出现的情况,**我们可以只有继续解决这个反复出现的问题。记得之前有一个案例,是一个普遍的问题:在一个实际的库中,我们分析发现它有内存泄漏,但我们并没有立即着手处理。结果第二天另一个库也出现了内存泄露,只好紧急重启。当时我们分析问题是某个BUG引起的后,就去查找那个BUG相关的资料,发现早在两三年前(2014年)就有同事已经解决了这个问题,但它仍然在另一个库中。对应的PATCH用于解决问题,但是是没有将这个问题扩展到所有系统,检查其他库是否也有这个问题导致的。从那时起,我们就特别关注这个普遍存在的问题。如果每个系统、每个问题都必须发生一次,成本太高,所以我们尽量在发现共性问题后解决,并尽量排除其他库。发生类似问题的情况。案例2第二个案例是Oracle数据库版本12.1.0.2。每天晚上,宿主机的CPU会时不时地持续到100%,应用程序会同时创建大量的数据到数据库中。当时我们的应急方案是把相关的等待时间全部杀掉,因为这些系统都在比较核心的库里,基本上每个系统杀掉几千个进程,成本还是比较高的。后来这个问题发生了两次之后,我们就开始着重分析问题。通过ASH分析,我们发现这个异常等待的发生是因为一条很简单的语句——SELECTUSERFROMSYS.DUAL。之后我们就用这个语句一步步关联起来,看看是在哪里调用的。原来是一个应用用户的登录TRIGGER中的用户判断步骤。这个USER是Oracle的内部函数,但是这么简单的一条语句,却让整个库都挂了。那么我们开始分析原因。我们通过ASH发现,在我们恢复应用之前,语句被重新加载了。当时我们怀疑是硬件引起的,就这样分析,发现是晚上10点Oracle的自动任务自动收集统计信息。采集之后,因为是一个登录Trigger,用户一直在登录,而在做登录解析的时候无法解析出这条语句,所以用户一直卡在那里。但是应用需要创建新的连接,而新创建的连接不能进库,会导致连接越来越多,全部卡在那里。***我们通过锁住双表统计的收集,从根本上解决了这个问题。案例三第三个案例有两个问题,后来发现这两个问题是同一个原因引起的。我们有一个从版本10.2.0.5.X升级到版本10.2.0.5.18的数据库。升级后,会时不时出现一些与cursor:pin相关的等待。其实出现cursor:pin是正常的,因为这个数据库的负载比较高,变化也大,但是问题是升级后才出现。运维认为这是升级后的异常,于是开始分析问题原因。第二个问题是在紧急情况下发现的。有时当异常发生时,某个库中某些语句的执行次数会非常高,甚至在15分钟内达到上亿次。对于一个正常的业务系统来说,出现这样高的执行频率是不正常的。后来我们分析了这些问题,发现这两个问题有相同点,比如语句中间有一个函数调用;例如,在这种情况下,如果A表访问的数据量很大,这些函数可能会被调用多次。我们发现有一条执行函数调用的语句可能会出现几十万次。如果调用过程中关联表的执行计划发生变化,比如A表全量扫描,可能会发生千万级的函数调用。当时我们也总结了一些关于用什么方法快速定位,是否是函数调用导致的意见。10g之前确实没有什么好办法,因为里面没有展示关联,只能通过代码扫一扫找到对应的语句。11g以后,就更简单了。通过与AS值相关的TOPLEVELSQLID,可以直接关联到导致问题的语句的功能。这里的另一个问题是函数调用。因为它调用的函数可能很快,但是次数可能比较多,性能波动可能影响比较大。之前,我们有一个案例发生在月底的高峰期。我们发现在某个数据库中会有大量的CBC在等待。后来发现有一个小表被频繁访问了。那个小表有100多行数据,但是可能相关语句每15分钟被调用几千万次。其实在这么高的并发下,出现这种CBC等待是很正常的。但是因为它只有100多行数据,而且都集中在一个数据块中,所以这个数据块特别热,这种CBC等待总会出现。于是我们就想办法解决这个热点问题,但是因为月末不能停止运行做改动,所以我们想出了一个方案:建立一个PCTFREE99索引,收集数据将表中的所有列都包含进去,保证每个索引块只保留一行数据,将100多行数据变相分成100多个块。做了这个操作之后,解决了CBC相关的问题,顺利度过了业务高峰期,但是第二天一早的报告又发现在坑里。因为我们需要在每个月的月初向监督汇报。这个报告是一个长事务,但是它也调整了那个报告中之前优化过的索引。优化后的语句虽然减少了CBC在高峰期的等待时间,但是由于需要访问100多个数据块,单次访问从0.25毫秒变成了1毫秒,相当于效率降低了4倍.由于报告是一个长期的事务处理,这意味着该过程比以前花费了两倍多的时间并且没有运行完。所以我们发现这个问题后,只好把那个索引杀掉,恢复到原来的状态。尤其是现在对IT的要求越来越高,时限要求也越来越高。很多系统基本都是采用这种敏捷开发的方式,尽快上线。新系统上线一个很大的问题就是刚上线的时候压力和负载都不会太高,但其实很多问题都隐藏在初期阶段。真正出现问题,负载高或者压力大的时候,解决起来会比较困难。尤其是数据库,当数据库量不大的时候,比如300、500M的数据,整这个表很简单,但是当表增加到300、500G甚至1、2T的时候,就想整这个表数据类的整改难度会大很多。比如我们之前在整顿staging表的时候就使用了这种在线重定义的方式,但是对于一些比较大的表,几百G甚至T的表,如果使用这种在线重定义的方式,就会遇到问题。到各种错误。后来坑太多了。现在对于大表的分表改造,我们先同步历史数据层面的改造,然后再做一次数据增量。方法会复杂很多。但实际上,如果我们在开始阶段就已经为这种大表设计好分区,尤其是在时间索引上,那么按照时间来分区可以避免很多问题。为什么我们的历史库中有这么多时间索引?一个很大的原因是很多报表是按时间查询的。比如你想查看本月或这一天的一些新数据,就需要通过时间字段来访问。之前看过很多时间索引,但是因为时间索引的特性,导致系统不断出现各种问题。如果在设计阶段就把这些大表提前设计成分区表,就可以完全避免这些不必要的问题。2、运维管理由于每个公司的具体情况不同,我简单介绍一下我公司的一些运维管理做法,供大家参考。1、变更管理相对来说,我们公司的变更管理是比较严格的,以后可能会更加严格。变更控制以变更控制为例,白天严禁进行任何变更,工作时间也不能进行任何变更。甚至一些紧急或故障维修,也需要部门负责人确认,领导批准,以确保风险可控。变革过程也许每个公司都有一个变革过程,但我们公司有一个特殊的地方。因为一些兼容数据库的要求可能会更高,所以流程控制的每一部分都必须到位。变更计划我们的变更计划需要大家提前做一个回顾和验证,包括制定计划的同事和执行操作的同事。变更实施者需要提前在一个环境中做一个完整的验证,以确保每一步都得到验证。通过。2.规范管理架构规范在我做架构师的时候,制定各种规范是一项重点工作。可能是自己养成了习惯,现在正在和大家一起制定各种运维规范。但是我感受最深的是:规范要有一个统一的标准,如果不统一,以后可能会出问题。比如我们之前的一些开发和测试环境就没有那么规范。现在我们要改造做自动化的时候,发现根本做不到,因为每个库的情况不一样,自动化脚本不可能适应所有情况去做这种标准化的改造.把它变成不标准很简单,但是把不标准变成标准就比较难了,尤其是在我们养成了习惯之后。运维规范2014年之前我们是做纯Oracle数据库运维,因为我们之前建立的是传统金融企业,运维都是Oracle数据库,但是2014年以后,我们逐渐转向互联网金融。因此,我们先后研究了MySQL、PG、Redis、MongDB、SQLServer、HBase等7、8个数据库,在运维过程中遇到了更多的坑。一开始有很多标准,但没有一个是最佳实践,很多都是根据行业和自己的经验制定的;在各种数据库中,不同的团队制定了不同的标准,***也有自己的标准。各种标准。所以,我们在运维中会发现各种各样的问题,在这方面强行规范整改是绝对必要的。而且,以往的标准大多没有经过大规模使用和大规模负载的验证,很多标准没有那么统一、规范和有效。因此,我们在运维过程中不断优化和完善这个规范。毕竟你遇到的情况不多的时候,你真的是没有办法解决这个问题的。规范优化比如一开始我们没有规定Redis一定要和应用放在同一个网络区域,但是随着Redis负载的增加,我们发现防火墙已经承受不住了。当时平安的WiFi刚刚上线,但是对于Redis的接入,有几个实例每秒调用数万次,整个防火墙无法支撑,差点造成严重故障。解决这个问题后,我们制定了一个强制规范:Redis这种高并发访问的数据库,必须和应用放在一起,不能出现跨墙访问。所以这个规范也在不断的优化,包括一些我们运维的标准。因为在标准刚制定的时候,我们可能没有考虑到一些问题,因为它已经很久没有使用了。印象最深的是MySQL刚推出时的一个问题。软件版本对小版本不明确。后来甚至出现停库时mysql版本是5.6.22,维护完后启动到5.6.16。最新版本是通过不断的优化,保证我们的规范和实际相结合,避免出现此类问题。3、人员发展团队意识关于团队,要提高团队中每个人的作用,要保证团队中的每个人都有后盾。如果变得不能留下任何人,对于球队的整体发展来说都是不正常的。所以,我们在安排工作的时候,对于比较重要的工作,我会尽量不让熟悉的同事重复去做,而是尽量让一些不熟悉的同事参与其中。每次高级成员离开时,团队的整体技能或知识就会明显缺失。为了避免类似的问题,我们从2017年开始制定了一些策略,让大家分享知识和技能。每周选择两个下午,每个下午选择一到两个小时进行分享。另一种策略是技能的积累,即把我们在工作中遇到和解决的一些问题录入到问题管理系统中。这样有两个好处:一是可以记录重复的问题,因为我们要分析哪些问题是重复的,哪些有共性,就需要有这样一个系统拉取相应的问题列表,第一是解决问题;二是即使人员流失,他们之前解决的一些问题和技巧也可以被团队的其他人发现,这样每一个离开的人都不会留下一个坑。所以,我们采用这种方式,是为了尽量避免人员流失或者团队变动带来的一些问题。但归根结底,没有办法完全避免这一点,因为数据库运维有一定的复杂性,需要靠不断的失败和解决方案,包括一些人为的错误来改善。权利和责任明确界定。我们的轮班人员7×24小时工作,即三班倒。该团队之前遇到了严重的问题。当有事情发生时,值班人员可能会把问题交给下一个班次;或者升级给别人后,他们觉得这与他们无关。另外,风险意识不强,有些操作在生产仓库操作前没有经过评估。那时候,我们也有很多问题。后来我们在内部重点提升了两点意识:责任人意识和风险意识。首先,在生产中采取措施之前,您需要确保操作的影响和后果。这个问题不能做完了再想:比如我们每天都在变,需要提前准备好脚本和手册,让值班人员熟悉。手术的后果是什么?后面会出现什么异常?对策是什么?……这些都需要提前评估。技能提升关于技能的提升,虽然必要的训练必不可少,但我们认为关键还是要靠自己在实践中的学习、理解和积累。没有好的捷径可以实现它。很多时候,还是需要不断的去解决问题,发现问题,甚至包括犯错的代价都可以得到提升。
