前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis异常排查实战:从问题定位到性能提升,助你成为技术领域的佼佼者!

Redis异常排查实战:从问题定位到性能提升,助你成为技术领域的佼佼者!

原创
作者头像
粲然忧生
发布2023-10-16 20:49:07
8541
发布2023-10-16 20:49:07
举报
文章被收录于专栏:工程师的分享工程师的分享

解决实际问题是学习知识的催化剂

大家好,前段时间,笔者所负责的一个模块出现了访问redis耗时较长的问题,在这个问题排查的过程中,对redis的问题思路和压测、调优进行了一些系统的学习和沉淀,在这里分享给大家

第一个重点,服务排障的基本方法

在岁月静好的一天,正当笔者准备下班工作的时候,突然,告警出现了!

嗯,又是一到下班就会告警!

仔细一看,原来是数据整体处理时间的慢了

既然慢了,就看看具体哪个链路慢了

看来是A模块的B阶段的处理耗时突然慢了

赶紧确认反向查询哪里出了问题,因为B阶段不是A模块的第一个阶段,所以基本排除是模块间的网络通信、带宽等问题

那这里有两个思路:

1.排查这个A模块本身的问题

2.排查数据量的问题

首先排查A模块本身的问题,这里的经验是

横向看基础指标,纵向看代码变更

1.1 先看A模块服务的基础资源数据

内存正常

CPU正常

1.2 再看所在Node节点的负载情况

Node负载正常,而且一般Node有问题,不会单服务出现问题

1.3 再看磁盘占用情况

存储节点一切正常

看起来,CPU、内存、网络IO、磁盘IO几个大的指标没有明显的变化

1.4 那是不是最近发布出现的问题呢?

经对项目成员对齐,A模块近期也没有进行发布

再排查下数据量的问题

2.1 那再看看有没有是不是数据量出现问题?

嗯,上报量确实增加了5倍,既然这样:

扩容、限流、服务降级三板斧搞上

问题解决了!

舒服!下班!

然后,在一个寂寞无人的深夜里,笔者突然惊醒!

问题当时虽然解决了,但其实并没有真的确认根本原因,数据量也许只是一个引子,这里会不会有性能优化的点呢?

在降本增效的大背景,性能优化在服务端更是一个重要的目标

于是,笔者翻身起床,又进一步的研究了一下

既然,问题出在A模块的B阶段

那首先进行细化,到底哪个方法出现了这么就耗时

排查耗时这里也有两个思路

1.业务打点进行排查

2.性能分析工具排查

一般来说,先用业务打点确认大概范围,然后通过性能分析工具精确确认问题点

之所以这样是因为有两个原因,

一是业务代码一般是造成问题的主要原因,在业务代码进行有效打点,可以更快的确认问题范围、变更范围、责任范围,从而可以有明确的责任人去跟进

二是一般来说性能分析工具排查都会定位到一些组件函数、系统函数,所以可能有很多个调用者、先用耗时打点的方式确认范围,可能更小的范围确认调用者,也就是疑似点,这样组合起来会比较

1.先打点确定业务范围

1.1 首先是修改了一下指标上报的代码,在监控面板上查看

1.2 然后是在模块的日志中进行耗时采集和输出:

结果都基本上定位到函数是redis的计数自增逻辑

至于为什么采用两种方法进行确认,是因为,实际业务会比较复杂,很多函数并不是线性调用,获取正确且精确耗时并不容易,需要多种方案去确认

2再用性能分析工具去定位精确函数

这里主要用的pprof工具,详细使用案例可参考后台填坑记——Golang的OOM的问题排查(二)

可以看到,紫色部分就是redis相关命令的调用耗时,基本上占了总耗时的大头

Double check!

事实上,这里笔者只是拿了一个函数作为例子,在这个场景下,基本上所有的redis相关的命令都慢到秒级,也就是说,Redis可用性出现了问题。

你看,我就说这个班没有白加

我们把问题从“数据量突增”转换到“Redis可用性”上来

嗯,这里我擅长!

毕竟,我们都喜欢把问题规约到以前解决的问题中(并没有)

第二个重点,Redis服务排障的基本方法

既然是Redis的问题,我们就看看到底如何排查Redis服务

其实,这里有很多的总结和文章,笔者这里主要是结合一个实际的问题,对思路进行再次整理

首先,我们的问题是:Redis服务请求的回包慢

这里的思路是:Redis服务本身问题——Redis数据存储问题——请求Redis的问题

一般来说业务出现问题的可能性>服务本身出现问题的可能性

单由于,业务模块没有太多变动,所以这次先查服务本身

1.Redis服务本身问题——Redis所在节点网路延迟问题确认

按照理论上来讲,应该首选确认是Redis服务本身的问题,还是节点网络的问题

这里引用kevine一篇文章的一段话

  • 业务服务器到 Redis 服务器之间的网络存在问题,例如网络线路质量不佳,网络数据包在传输时存在延迟、丢包等情况 网络和通信导致的固有延迟: 客户端使用TCP/IP连接或Unix域连接连接到Redis,在1 Gbit/s网络下的延迟约为200 us,而Unix域Socket的延迟甚至可低至30 us,这实际上取决于网络和系统硬件;在网络通信的基础之上,操作系统还会增加了一些额外的延迟(如线程调度、CPU缓存、NUMA等);并且在虚拟环境中,系统引起的延迟比在物理机上也要高得多 结果就是,即使 Redis 在亚微秒的时间级别上能处理大多数命令,网络和系统相关的延迟仍然是不可避免的

可以看到,事实上,即使到物理硬件层,网络的延迟还是有的但不大,但加上Redis所在机器上的带宽限制和网桥性能等问题,这个问题可能会到达不可忽略的地步

事实上,在这个案例中,基本排除是节点网络的问题,

一是,当数据量下降的时候,redis的回包耗时减少。

二是,Redis服务是集群内服务,通过监控发现,内网带宽并没有突破限制

2.Redis服务本身问题——Redis自身服务网路延迟问题确认

对于单实例:这里有两个比较经典的命令

2.1 redis-cli -h 127.0.0.1 -p 6379 --intrinsic-latency 60

即在 Redis server 上测试实例的响应延迟情况

可以看到,还是响应还是挺快的

不过,这个是一个瞬时速度,需要现场抓,所以在复现问题上来说,不是那么的好用,所以可以稍微调整下命令

2.2 redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1

查看一段时间内 Redis 的最小、最大、平均访问延迟:

可以看到,也没啥问题

2.3 吞吐量(使用info stats)

具体含义如下:

代码语言:javascript
复制
# 从Rdis上一次启动以来总计处理的命令数
total_commands_processed:2255
# 当前Redis实例的OPS,redis内部较实时的每秒执行的命令数
instantaneous_ops_per_sec:12
# 网络总入量
total_net_input_bytes:34312
# 网络总出量
total_net_output_bytes:78215
# 每秒输入量,单位是kb/s
instantaneous_input_kbps:1.20
# 每秒输出量,单位是kb/s
instantaneous_output_kbps:2.62

其实看到这里,相信很多同学会发现,这个吞吐量要是有时序图好了,嗯,事实上,这也就是为啥很多服务要配置Prometheus的原因:

(图来源于网络)

对于多实例:还要考虑主从同步的问题

主要关注:master_link_down_since_seconds、master_last_io_seconds_ago、master_link_status等指标

使用info Replication命令

不过一般用上了主从同步这一套,基本上业务就会比较重了,运维同学也会在早期建立起监控

回到问题,这里服务没有用主从同步的方式,所以,这里的疑似点排除

3. Redis服务本身问题——CPU、Memory、磁盘IO

其实从前面排障可知:内存、CPU、IO、磁盘应该是最基本的指标,这里之所以先查网络IO,是因为IO的疑点最大,其他的基本上可以通过 info memory;info CPU;info Persistence命令来查看,这里有一个详细的表格供大家参考,回到问题,查看的信息如下:

可以看到CPU比较高,快到了90%

这里补充一个知识点:

cpu 这里,除了上面的内容外:

还有一个场景绑定固定cpu核心的设置,在redis6.0上有,有兴趣的同学可以搜一下

memory这里,主要关注三个指标:

used_memory_rss_human:表示目前的内存实际占用——表示当前的内存情况

used_memory_peak_human:表示内存峰值占用——表示曾经的内存情况(主要是用来抓不到现场的时候查问题用的)

mem_fragmentation_ratio:这里引用kevine一篇文章的一段话

内存碎片率( mem_fragmentation_ratio )指标给出了操作系统( used_memory_rss )使用的内存与 Redis( used_memory )分配的内存的比率 mem_fragmentation_ratio = used_memory_rss / used_memory 操作系统负责为每个进程分配物理内存,而操作系统中的虚拟内存管理器保管着由内存分配器分配的实际内存映射 那么如果我们的 Redis 实例的内存使用量为1 GB,内存分配器将首先尝试找到一个连续的内存段来存储数据;如果找不到连续的段,则分配器必须将进程的数据分成多个段,从而导致内存开销增加,具体的相关解释可参考这篇文章:Redis内存碎片的产生与清理 内存碎片率大于1表示正在发生碎片,内存碎片率超过1.5表示碎片过多,Redis 实例消耗了其实际申请的物理内存的150%的内存;另一方面,如果内存碎片率低于1,则表示Redis需要的内存多于系统上的可用内存,这会导致 swap 操作。内存交换到磁盘将导致延迟显著增加 理想情况下,操作系统将在物理内存中分配一个连续的段,Redis 的内存碎片率等于1或略大于1

这里其实隐含了一个知识点

作为内存型数据库,磁盘也是一个关键点:这里包含了两个方面(1.持久化 2.内存交换)

持久化是一个比较容易忽略的问题,但其实在集群模式下,持久化可能也会从侧面发现问题,这里可以关注如下几个点:

查询的信息有这个几个:

还有一个比较特殊:latest_fork_usec,这个基本上是跟宿主机的关系比较大,如果耗时较久,一般会出现在ARM等机器上

具体参考

内存交换这里其实也是一个关键点:

这里主要关注的点是:maxmemory、maxmemory-policy、evicted_keys

一是最大内存限制maxmemory,如果不设这个值,可能导致内存超过了系统可用内存,然后就开始swap,最终可能导致OOM

二是内存驱逐策略maxmemory-policy,如果设了maxmemory这个值,还需要让系统知道,内存按照什么样策略来释放

这里补充个知识点Reids4.0之后可以将驱逐策略放在后台操作,需要这样设置

代码语言:javascript
复制
lazyfree-lazy-eviction yes

三是驱逐数:evicted_keys,这个可以通过info stats查看,即采用驱逐策略真正剔除的数据数目

四是内存碎片率,在上面的引用已经给出了,内存碎片率低的情况下可能导致swqp

你看,这里其实是内存和磁盘IO的联动点

回到问题

从上面的截图可以看到,除了CPU外,基本指标是正常的(maxmemory虽然没设,但内存远没到限制)

那么再来查查Redis数据存储的问题

1. Redis数据存储的问题——key的总数

命令为info keyspace,主要是redis实例包含的键个数。

这里单实例建议控制在1kw内;单实例键个数过大,可能导致过期键的回收不及时。 之所以先查这个是因为,这里很可能是一个容易被人忽略的点,可能每个业务的量不大,但最后一个业务成为压死骆驼的最后一根稻草,所以有问题先排查这里

这里可以看到,总key的数目是没有超过限制的,问题点不在这

2. Redis数据存储的问题——Bigkey、内存大页

所谓大key,就是耗时久的key,具体定义,嗯,这里是bigkey的危害:

Redis 阻塞 :因为 Redis 单线程特性,如果操作某个 Bigkey 耗时比较久,则后面的请求会被阻塞。 内存空间不均匀 :比如在 Redis cluster 或者 codis 中,会造成节点的内存使用不均匀。 过期时可能阻塞 :如果 Bigkey 设置了过期时间,当过期后,这个 key 会被删除,假如没有使用 Redis 4.0 的过期异步删除,就会存在阻塞 Redis 的可能性,并且慢查询中查不到(因为这个删除是内部循环事件)。 导致倾斜 :某个实例上正好保存了 bigkey。bigkey 的 value 值很大(String 类型),或者是 bigkey 保存了大量集合元素(集合类型),会导致这个实例的数据量增加,内存资源消耗也相应增加。实例的处理压力就会增大,速度变慢,甚至还可能会引起这个实例的内存资源耗尽,从而崩溃。

总之就是查询和删除容易造成堵塞,所以要专门看一下,当然这里还有一个关联的知识点:

内存大页:

内存页是用户应用程序向操作系统申请内存的单位,常规的内存页大小是 4KB,而Linux 内核从 2.6.38 开始,支持了内存大页机制,该机制允许应用程序以 2MB 大小为单位,向操作系统申请内存

由于系统采取的COW(写时复制)的方案、如果频繁写请求操作的是一个 bigkey,那主进程在拷贝这个 bigkey 内存块时,涉及到的内存大页会更多,时间也会更久,从而延迟比较久

具体命令:redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01

可以看到,没有bigkey,内存大页也没开启,问题点也不在这里

3. Redis数据存储的问题——key集中过期

除了当数据库用,基本上redis里面的key都会设置过期时间,这时有可能存在集中过期导致负载过高的问题

这里补一个知识点:过期策略和驱逐策略的区别

看不清的话,就点上面的链接

回到主线,结论就是redis的定时过期策略虽然会有时间上限限制,但依然在大量过期的情况下出现延时高的情况

不过,这里与这个问题应该关系不大,因为如果是过期问题,不会仅出现在数据量大的情况发生,应该是“周期性出现”,所以问题点也不在这里

既然不是数据本身的问题,那再看看是不是访问的问题

1. 请求Redis的问题——客户端连接数、阻塞客户端的数

这里可能会奇怪,为什么查这个数据,一般业务服务链接redis的请求不是通过客户端,嗯,就是因为问题很少可能出现在这里,所以先查这里

连接数430个、阻塞数0个,没有超过限制,所谓问题不在这里

阻塞的经典函数包括: BLPOP, BRPOP, BRPOPLPUSH

2. 请求Redis的问题——慢命令

即查看请求的命令耗时多久

如下图:

第一个命令是指保留慢命令的条数:128

第二个命令是慢命令的标准 1000毫秒

第三个命令是查看慢命令的top 2和top 3

第一个值是id

第二个值是执行的时间点

第三个值是执行时间

第四个值是执行的命令

这里对几个慢命令的时间进行了查询,发现与redis回包慢的时间不相符,而且并没有太慢,故问题也不在这里

当然这里如果有问题,可以参考

3. 请求Redis的问题——缓存未命中

这里主要是看info stats的两个值:

不过这里并不是这个问题的重点,通过梳理业务逻辑得知,并没有未命中就去持久化数据库再去查询的逻辑

4. 请求Redis的问题——Hotkey

反过来,会不会是访问了某个点太多次了,在redis4.0.3之后,可以查hotkey的情况

当然,要先把内存逐出策略设置为allkeys-lfu或者volatile-lfu,否则会返回错误:

具体可以参考

这里有个小细节,笔者负责的模块是redis4.0.0,刚好没有hotkey监控,然后笔者尝试升级了redis到5.0.0

依然也没有,最后发现还需要升级业务服务的redis的组件库(pakeage)

还好,笔者的负责的服务自己构建了一个热度统计

嗯,看起来hotkey的问题确实存在,好,我们继续

经过以上三个方面的排查:

我们发现:CPU高、hotkey明显

这里隐含了一个点:与CPU相对的是OPS并没有很高

也就是说,虽然Redis很忙,但似乎并没有很高的服务产出——对,这句话用在工作上有时也挺合适🐶

一般遇到这个情况,我们就要仔细分析下,到底Redis的CPU消耗在哪里了

这里就要仔细分析下Redis的服务架构了

Rrdis6.0之前,主要采用的单线程模式,为什么采取单线程

官方的回答是:

核心意思是:CPU 并不是制约 Redis 性能表现的瓶颈所在,更多情况下是受到内存大小和网络I/O的限制,所以 Redis 核心网络模型使用单线程并没有什么问题,如果你想要使用服务的多核CPU,可以在一台服务器上启动多个节点或者采用分片集群的方式

除了上面的官方回答,选择单线程的原因也有下面的考虑。在这篇文章中:

使用了单线程后,可维护性高,多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。

同时,redis的设计者也是简单主义者,这里可以从Redis为什么不支持事务回滚上得到答案

回到正题

对于文件事件来说结构图是下图

单线程处理逻辑是:

图中的蓝色部分是一个事件循环,是由主线程负责的,可以看到网络 I/O 和命令处理都是单线程,所以看起来这里会有挺多的网络IO在里面,那这里会不会有坑呢?

其中,在Redis 6.0 新特性-多线程连环13问!

指出:

Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

• 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式

• 使用多线程充分利用多核,典型的实现比如 Memcached。

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

• 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

• 多线程任务可以分摊 Redis 同步 IO 读写负荷

咦,这里是不是就是问题的关键呢?

笔者负责的Redis的版本是5.0

且QPS这里也比较符合

事实上,关于网络IO占用CPU问题的深究来说,这篇文章里面有说明

Redis 6.0 以前的单线程网络 IO 模型的处理具体的负载在哪里呢?虽然 Redis 利用epoll机制实现 IO 多路复用(即使用epoll监听各类事件,通过事件回调函数进行事件处理),但 I/O 这一步骤是无法避免且始终由单线程串行处理的,且涉及用户态/内核态的切换,即:

  • socket中读取请求数据,会从内核态将数据拷贝到用户态 (read调用)
  • 将数据回写到socket,会将数据从用户态拷贝到内核态 (write调用)

按照文章中的介绍:上面的红色部分会导致CPU的消耗——说好的Redis的瓶颈不在CPU呢!

第三个重点,复现问题和测试的基本方法

那如何确认这里的猜测呢?

按照先Demo确认——再模拟线上服务的方式

1.本地Demo确认:

既然是网络IO多,那怎么减少网络IO呢?

两个方案:一个是pipeline、一个是Lua脚本

这里

首先kevine同学了做了pipline的测试,并用perf做了检测,发现上下文切换次数却是少了!

2.线上模拟服务:

这里就会比较麻烦,因为一到线上,数据链路就会比较长

我们先简化一下服务链路

质量同学的角度上看:压测讲究的事是全链路模拟,不然就无法做上线前的最后的质量守护

开发同学的角度上看:复现问题最好用到最低的资源,因为更多的资源意味着更多的人力成本、时间成本、沟通成本

两个角度都没有问题,事实上,笔者认为,只有从两个角度看问题,才能更好的平衡质量和效率

在这里,笔者主要用的开发角度尽快的复现问题,所以选择模拟kafka的数据消费,然后给到服务Y压力,最终将压力传导到Redis中,具体工具使用kaf

1.首先将kaf装在了kafka的一个服务中

2.然后抓取线上的数据30条(之所以抓的这么少,是因为线上就是hotkey的问题,这里模拟的就是大量相似数据访问的场景)

3.使用kaf给到10000次的生产数据

cat xxx-test | kaf produce kv__0.111 -n 10000 -b qapm-tencent-cp-kafka:9092

结果,压力不够

for i in {0..8};do cat xxx-test | kaf produce kv__0.111 -n 10000 -p ${i} -b qapm-tencent-cp-kafka:9092 ; done

结果,压力还是不够

那就将kaf装在每一个kafka的服务中

结果:

有效果,但Redis的CPU还是不是很高

看下监控

啊,服务Y的CPU一直也不高,看来,服务Y并没有感受到压力

给服务Y加协程,CPU上来了

但内存提前满了

加内存

这里其实也是一个资源调优的经验,事实上,一个服务的内存和CPU的比例关系需要结合线上的负载来看,而且要定期看,不然也会导致资源浪费

好的,CPU和内存都调整了,搞起!

终于,Redis的CPU上去了!

看下OPS

嗯,OPS低、CPU高,问题复现了!

果然:hotkey其实就会导致cpu变高,而这时,因为大量的cpu都在数据切换和存储上,导致其他的请求比较慢

现象对上了!

那如何解决呢?

也是三板斧:

1.如果是多实例的话,就是经典的读写分离!

2.如果是单实例的话,就使用pipeline批量写入

3.如果pipeline无法满足业务的话,就在业务服务只加一层缓存

毕竟滑铁卢大学教授 Jay Black 的一句名言:

计算机科学中的每个问题都可以用一间接层解决

基于服务现状,笔者选择了第三种方案:

然后,服务耗时降下来了!

cpu也降下来了

完美!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一个重点,服务排障的基本方法
    • 既然慢了,就看看具体哪个链路慢了
      • 1.排查这个A模块本身的问题
        • 2.排查数据量的问题
          • 扩容、限流、服务降级三板斧搞上
            • 1.业务打点进行排查
              • 2.性能分析工具排查
              • 第二个重点,Redis服务排障的基本方法
              • 第三个重点,复现问题和测试的基本方法
                • 1.本地Demo确认:
                  • 2.线上模拟服务:
                  • 计算机科学中的每个问题都可以用一间接层解决
                  相关产品与服务
                  云数据库 Redis
                  腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档