IT教程 ·

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图片

参与评论