读完需要9分钟
速读仅需7分钟。
前几天写了一篇半成品:
我的本意是先抛出一个系统层的解决思路,然后引出更有张力的解决方案,但是当时方案还没有验证完,不足为凭,最近的对比测试结果出来了,我就把一些结果附上。
如下是最近一段时间的延迟情况,如果从库延迟阈值超过了100秒,我们就会收到相关的报警,所以从整体的趋势来看,总有那么几天的数据情况会比较高。
对于这个延迟问题的处理,算是花费了一些心思,首先业务层的逻辑相对是比较简单的,大体如下:
在缓存层面进行校验,查看数据的状态,
select xxxxx from test_list where uid=xxxx;
如果存在且失效,则进行更新:
update test_list set xxxx where uid=xxxx;
如果不存在则进行数据插入:
insert into test_list values(xxxx,xxxxx);
而且从整体的变更量来看也没有那么大,基本是百万级别,所以初期的分析是想在系统层,数据库配置层做一些改进。
这个数据库是基于Percona 5.7.16的版本,已经打开了GTID和并行复制功能,相关的表结构比较简单,是用户维度的状态数据。
表结构类似下面的形式:
CREATE TABLE `mydata_list_0` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
。。。。
`t2` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uid` (`uid`),
KEY `idx_r` (`r`)
) ENGINE=InnoDB AUTO_INCREMENT=28035491 DEFAULT CHARSET=utf8 COMMENT='数据列表'
对于这种状态,问题看起来是比较明显的,因为数据是基于用户维度uid,相关的约束为唯一性约束,也就意味着每个用户对应一条唯一标识的记录,表里还存在一个自增id,经过和业务沟通,其实这个自增id在业务层也没有实际的使用场景,所以经过讨论和评估,我们决定先去掉这个自增列id,整个过程可以使用pt工具实现在线的处理,重构数据的过程,从库端也没有任何的延迟。
修复完问题之后的延迟情况如下,第1,2个竖线是变更前的延迟情况,第3个是变更后的延迟情况,从后面几天可以看到整体的变更容量是动态的,总体上是明显低于之前的延迟状态的,所以自增列的修正是整个延迟修复的第一板斧。
但是修正了自增列的冗余问题之后,对于问题的实质性改进不大,延迟依然存在,对于我使用了很多日志分析,比如分析binlog的数据,分析慢日志数据,甚至打开了部分的general log来做补充分析,整体上来看,对于业务逻辑侧所做的改进是比较有限的。
尝试调整GTID模式下的一些优化参数,但是没有明显效果。
从复制层面的改进来说,一种偏方就是直接用高版本解决,所以我临时启用了MySQL 8.0的新版本,想看看在同样的复制模式下,MySQL 8.0的表现如何。如果高版本能够解决问题,其实直接升级的动力就更足了,当然很多同学知道MySQL 8.0的一个复制改进就是writeset,是在主库开启生效,目前我们还是以稳定为主,暂不能直接升级到MySQL 8.0,所以就折中做了一个8.0的从库先跑跑看,整体下来就是这样的一套复制模式,一主多两从,对应不同的版本。
在同一台服务器上,不同端口,开启近似的缓存,两者的延迟情况差别很大,红色的部分是Slave配置了writeset参数的延迟情况,而绿色的线是已有的复制模式。
可见无脑升级到MySQL 8.0不是正确的姿势,也确实让撞大运的心理受到了打击,所以单纯调参数,升版本的收益没有想象那么大,当然主库升级到MySQL8.0可能效果会好一些,但是限于当前环境,没有继续往下走。
延迟问题的修复如果要继续往下走,一定要知己知彼,所以这个阶段,我开始找研发同学分析他们相关的代码逻辑,并结合当前的负载情况进行“回放”,这个分析的过程是比较繁琐的,此处省去5000字,最后我得出了两点结论:
1)整个数据稽核的过程中,上下文切换非常频繁,产生了大量的事务,因为刷新MySQL层的数据,需要结合Redis层的缓存数据作为依据,如果数据过期需要刷新缓存,同时需要刷新MySQL数据,这个过程中是一边刷Redis一边刷MySQL,看起来好像是比较合理的,但是细想,在数据库层面其实会看起来有一种假象,那就是数据库层面的处理时间其实是包含了刷新缓存的等待时间,在处理模式上,建议开发同学使用基于批量刷新的模式,能够在最大程度上控制事务的粒度。举个更容易理解的例子,如果写入100万数据,是使用100万条SQL写入,默认开启100万个事务,还是启用100个,甚至1个事务,哪个的处理代价更低,显然是后者,这个是有很鲜明的数据作为对比补充,从之前的测试来看,至少差距有8倍以上。
2)表中依然存在几个冗余字段,从general log中做了细致的分析,发现有部分旧服务还是会维护那几个冗余字段,但在早高峰的数据稽核过程中是不涉及到这几个字段的,和开发同学做了确认,这个确实是遗留的一个服务,而且这几个字段确实已经不对外提供服务了,可以在经过确认后去除。
所以事情到了这个时候,空间就大了许多,我们先来和业务同学商定控制事务的粒度,实现批量提交刷新。
如下是近几天的延迟情况,可以看到最后的一个“羞涩”的小点那就是今天的延迟情况,明显比原来要低了很多,这是在同一个基线下对比情况,可以看到实现了批量刷新之后的效果是很明显的。
接下来要处理的就是两件补充的事宜了,可以直接停掉MySQL 8.0的Slave节点,这样延迟会低一个数量级,然后去除几个冗余的字段,整个服务的延迟情况要达到近乎于0只是时间问题。
通过这个过程,也让我对对于优化层面从真实的业务场景入手,进行调整的改进有了更进一步的认识。