redis系列-14点的灵异事宜
从实战角度超级详解中大型企业微服务化的六大核心关键技术
概述
项目组天天14点都邑遭受惊魂时刻。一条条告警短信把工程师从午后小憩中拉回实际。今后问题又神奇消逝。是PM喊你上工了?照样效劳器给你开顽笑?下面请看工程师怎样一步一步揪出真凶,处理问题。
假如不想看故事,能够直接跳到末了章节下看和Redis相干部份。
原由
某天下昼,后端组的监控体系发出告警,效劳器相应时刻变长,超过了阈值。过一会儿体系自动恢复了,告警消除。
第二天差不多的时刻点,监控体系又发出了一样的告警,过几分钟后又恢复了。
我们决议排查这个问题。
背景
起首要引见一下运用的架构,很简单的三层架构的web效劳。
从外到内大概是如许的
- Load Balance:对外供应接见进口,对内完成负载平衡。
- Nginx:放在LB背面,完成流控等功能。
- App Service :逻辑效劳,多机多历程。
- Storage:MySQL和Redis组成的存储层。
我们的效劳布置在aws云上,架构里用到了许多aws的效劳。
比方ELB,EC2,RDS等
表象
排查问题的第一步就是要收集信息。从监控和日记体系里提取大批的相干信息,然后再剖析、处理问题。
我们收集到的信息大概是如许的
在天天14点的时刻
- QPS突增
- P99目标升高
- App效劳器集群CPU、内存都升高,tcp衔接数狂涨,入网流量下降
- MySQL Write IOPS升高,写入延时升高,数据库衔接数升高
几分钟后,这些目标都回归到一般程度。
排查
起首从代码入手,看看是否是有这个时刻点的定时使命。效果发明并没有。
然后就是第一个疑心对象MySQL
运用mysqlbinlog敕令统计一下各个时刻点的binlog数目
日记条数 | UTC时刻 |
---|---|
624 | 6:00:37 |
396 | 6:00:38 |
933 | 6:00:51 |
834 | 6:00:52 |
402 | 6:01:24 |
2019 | 6:01:25 |
6030 | 6:01:26 |
7362 | 6:01:27 |
1479 | 6:01:28 |
1026 | 6:01:29 |
1320 | 6:01:30 |
954 | 6:01:31 |
我们又在第二天的这个时刻点视察了一下现场
运用show processlist敕令抓取一下当时MySQL衔接的状况,效果发明来自App效劳器的衔接居然都sleep了20秒摆布,什么事儿都没做。
开端推论
依据以上的数据能够复原一下当时的场景
- App效劳器socket数激增
- App效劳器不再举行逻辑处置惩罚(这个待确认)
- App效劳器不再举行任何数据库操纵
- App效劳器恢复读写数据库
- 积存了大批的收集要求 让游戏效劳器cpu增添
- 大批量的写要求涌向数据库 形成数据库各项目标升高
那末问题来了
- 激增的socket来自那里?
- 或许去衔接了谁?
- App效劳器为何会长达20秒没有什么数据库操纵?
- App效劳器是真的hang住了?
带着疑问入手动手进一步排查
深切排查
先处理第一个问题 多出来的socket来自那里?
App Service
在14点前 选一台App效劳器,抓取它的tcp衔接
#!/bin/bash
while [ true ]; do
currentHour=$(date +%H)
currentMin=$(date +%M)
filename=$(date +%y%m%d%H%M%S).txt
if [ $currentHour -eq 05 ] && [ $currentMin -gt 58 ]; then
ss -t -a >> $filename
#/bin/date >> $filename
elif [ $currentHour -eq 06 ] && [ $currentMin -lt 05 ]; then
ss -t -a >> $filename
#/bin/date >> $filename
elif [ $currentHour -ge 06 ] && [ $currentMin -gt 05 ]; then
exit;
fi
/bin/sleep 1
done
对大小差别比较大的文件举行比对,发明多出来的衔接来自Nginx。
Nginx只是个代办,那就排查它的上游Load Balance。
Load Balance
Load Balance我们运用的是aws的典范产物ELB。
ELB的日记很大,主如果剖析一下这段时刻内有无非常的流量。
经由对照剖析 13:55-14:00 和14:00-14:05 这两个时刻段的要求上没有显著的差别。基本上都是有以下要求组成
然则从14:00:53入手动手,带gateway的要求大部份都返回504,带time的要求都一般返回。
504示意网关超时,就是App相应超时了。
依据这个信息有能够推断出一些状况
- App Service还在一般供应效劳,不然time要求不会一般返回
- App Service一切写数据库的操纵都处于守候的状况
- Nginx到App Service的衔接得不到及时处置惩罚,所以衔接很长时刻没有断开,致使了Nginx和App Service的socket衔接快速增长
依据以上,基本上能够消除是ELB,Nginx带来的问题。
那末问题就剩下一个,什么数据库长时刻不可写呢? 而且天天都在牢固时刻。
MySQL
问题又回到了数据库上,起首想到的就是备份,然则我们的备份时刻不在出问题的时刻段。
我们运用的是aws的RDS效劳,查阅了一下RDS关于备份的文档。
只要在数据库备份的时刻才大概会涌现写I/O挂起,致使数据库不可写。 而默许的备份时刻窗口是如许的。
地区 | 时刻窗口 |
---|---|
美国西部(俄勒冈)地区 | 06:00–14:00 UTC |
美国西部(加利福利亚北部)地区 | 06:00–14:00 UTC |
这个入手动手的时刻也恰好在我们出问题的时刻,简直是太偶合了。
下一步就是要确认这个问题。
是在偷偷的帮我们做备份,照样实例地点的物理机上的其他实例滋扰了我们?
在某个MySQL实例上建个新的数据库test,建立一张新表test。
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`curdate` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
每秒钟往这张内外写条数据,数据的内容是当前时刻,如许就可以看出来在哪一个时刻段数据库不可写了。
同时每秒钟读取这张表的最大值,记录下效果和当前时刻,如许就可以看出来哪一个时刻段数据库不可读。
测试的剧本以下
#!/bin/bash
host=xxxx.xxx.xxx
port=3306
user=xxxxx
password=xxxxx
mysql="mysql -u$user -p$password -h$host -P$port --default-character-set=utf8 -A -N"
fetchsql="show processlist;"
selectsql="select max(id),now(3) from test.test;"
insertsql="insert into test.test(curdate) value(now(3));"
function run(){
filename_prefix=$1
mysqlcmd="$($mysql -e "$fetchsql")"
echo $mysqlcmd >> $filename_prefix.procs.txt
mysqlcmd="$($mysql -e "$selectsql")"
echo $mysqlcmd >> $filename_prefix.select.txt
mysqlcmd="$($mysql -e "$insertsql" )"
}
while [ true ]; do
currentHour=$(date +%H)
currentMin=$(date +%M)
filename_prefix=./checksql/$(date +%y%m%d%H%M%S)
$(run $filename_prefix)
if [ $currentHour -eq 05 ] && [ $currentMin -gt 59 ]; then
$(run $filename_prefix);
elif [ $currentHour -eq 06 ] && [ $currentMin -lt 02 ]; then
$(run $filename_prefix);
elif [ $currentHour -ge 06 ] && [ $currentMin -gt 02 ]; then
exit;
fi
/bin/sleep 1
done
这个剧本同时还每秒钟扫描一次MySQL各个客户端的事情状况。
末了获得的结论是,涌现问题的时刻点,数据库可读也可写。
问题彷佛陷入了逆境。疑心的点被逐一证实没有问题。线索也断了。只能再回到起点了,继承从代码动手,看看那里会形成单点,那里涌现了问题会让一切的游戏效劳器团体卡住,或许是让数据库操纵卡住。
Redis
终究排查到了罪魁祸首主角。
最可疑的有两个点
- 数据库分片的计划依靠Redis。Redis里存储了每一个用户的数据库分片id,用来查找其数据地点的位置。
- 用户的装备和逻辑id的映照关联,也存储在Redis里。险些每一个API要求都要查找这个映照关联。
以上两点险些是一个API要求的入手动手,假如这两点涌现了问题,后续的操纵都邑被卡住。
经由和ops确认,这两个Redis的备份时刻窗口确着实6:00-7:00utc。而且备份都是在从库上举行的,我们程序里的读操纵也是在从库上举行的。
经由历程Redis的info敕令,参考Redis近来一次的备份时刻,时刻点也恰好都在北京时刻14:01摆布。
进一步确认怀疑
把两个Redis的备份时刻做出变动。Redis1更换为3:00-4:00utc,Redis2更换为7:00-8:00utc。
北京时刻 11:00摆布 redis1一般备份。问题没有复现。
北京时刻 14:00摆布 问题没有复现。
北京时刻 15:00摆布 redis2一般备份。问题复现。
预先查看了一下redis1和redis2的数据量,redis2是redis1的5倍摆布。
redis1占用内存1.3G摆布,redis2占用内存6.0G摆布。redis1的备份历程险些在霎时完成,对App的影响不显著。
结论
问题涌现的大抵历程是如许的
- redis2在北京时刻的14点摆布举行了从库备份。
- 备份时期致使了悉数reids从库的读取操纵被阻塞住。
- 进一步致使了用户的api要求被阻塞住。
- 这时期没有举行任何数据库的操纵。
- 被逐步积聚的api要求,在备份完成的一小段时刻内,给Nginx,App Service,Redis,RDS都带来了不小的打击
- 所以涌现了前文中形貌的征象。
预先烟
实在问题的泉源还在Redis的备份上,我们就来聊一下Redis的备份。
Redis的耐久化能够分为两种计划;一种是全量体式格局RDB,一种是增量体式格局AOF。
概况能够参考。
RDB
把内存中的悉数数据按花样写入备份文件,这就是全量备份。
它又分为两种差别的情势。涉及到的Redis敕令是SAVE/BGSAVE。
SAVE
总所周知,Redis效劳都单线程的。SAVE和其他罕见的敕令一样,也是运行在主历程里的。可想而知,假如SAVE的行动很慢,其他敕令都得列队等着它完毕。
BGSAVE
BGSAVE敕令也能够触发全量备份,然则Redis会为它fork出来一个子历程,BGSAVE敕令运行在子历程里,它不会影响到Redis的主历程实行其他指令。它唯一的影响大概就是会在操纵体系层面上和Redis主历程合作资本(cpu, ram等)。
AOF
增量的备份体式格局有点像MySQL的binlog机制。它把
会改变数据的敕令都追加写入的备份文件里。
这类体式格局是redis效劳的行动,不对外供应敕令
对照
两种体式格局可有优缺点。
- RDB文件较小,自定义花样有上风
- AOF文件较大,虽然Redis会兼并掉一些指令,然则流水账照样会很大
- RDB备份时刻长,没法做到细粒度的备份
- AOF每条指令都备份,能够做到细粒度
- 两者能够连系运用
Amazon ElastiCache for Redis
我们运用的是aws的托管效劳,他们是怎么做备份的呢?
概况能够参考
Redis 2.8.22之前
运用BGSAVE敕令,假如预留内存不足,大概会致使备份失利。
Redis 2.8.22及今后
假如内存不足,运用SAVE敕令。
假如内存足够,运用BGSAVE敕令。
大提要预留若干内存呢?aws官方引荐25%的内存。
很显然我们的实例的预留内存是不够这个数的,所以致使了问题的涌现。
我以为aws能够把备份做的更好。
Java 添加、读取、删除Excel图片