Redis:性能风险之定位及解决Redis变慢

【关于作者】

关于作者,目前在蚂蚁金服搬砖任职,在支付宝营销投放领域工作了多年,目前在专注于内存数据库相关的应用学习,如果你有任何技术交流或大厂内推及面试咨询,都可以从我的个人博客(https://0522-isniceday.top/)联系我

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9f3aaWr3-1681039568571)(https://zhangyuxiangplus.oss-cn-hangzhou.aliyuncs.com/boke/Redis变慢的定位及解决.png)]

1.问题认定

1.1.查看Redis的响应延迟

这种方法是看Redis延迟的绝对值,但是,在不同的软硬件环境下,Redis本身的绝对性能并不相同

1.2.当前环境下的Redis基线判断

基线性能:redis实例在系统低压力、无干扰下的基本性能。

redis 2.8.7版本提供了–intrinsic-latency选项,用来测试和统计测试期间内的最大延迟,这个延迟可以作为Redis的基线性能

其中,测试时长可以用–intrinsic-latency选项的参数来指定。

举个例子,比如说,我们运行下面的命令,该命令会打印120秒内监测到的最大延迟。可以看到,这里的最大延迟是119微秒,也就是基线性能为119微秒。一般情况下,运行120秒就足够监测到最大延迟了,所以,我们可以把参数设置为120

./redis-cli --intrinsic-latency 120
Max latency so far: 17 microseconds.
Max latency so far: 44 microseconds.
Max latency so far: 94 microseconds.
Max latency so far: 110 microseconds.
Max latency so far: 119 microseconds.

36481658 total runs (avg latency: 3.2893 microseconds / 3289.32 nanoseconds per run).
Worst run took 36x longer than the average latency.

1.3.比较两者下结论

比较响应延迟和基线性能,如果响应延迟比基线性能慢很多,那就可以认定服务端响应确实很慢

基线性能也只是考虑了服务器的软硬件环境的影响,还可能存在网络方面的影响

1.3.网络对Redis的性能影响

利用iPerf,测量Redis客户端到服务端的延迟情况

2.系统性排查及应对Redis变慢的方案

当我们定位到Redis确实变慢了之后,我们之后该如何查找原因并解决呢?

从下图中Redis自身特性、操作系统、文件特性三个角度出发,排查和解决Redis变慢的问题

img

2.1.Redis自身操作特性的影响

Redis键值对操作对延迟性能的影响,重点是两类操作:慢查询命令和过期key操作

2.1.1.慢查询命令

慢指令:需要提前知道不同指令的一个操作复杂度,根据复杂度能够很快的定位到慢指令,具体复杂度看Redis官方文档

如何查看慢查询(再结合具体命令的时间复杂度):

  • Redis日志

    查看slowlog是否存在SORT/SUION/ZUNIONSTORE/KEYS、或slowlog出现很多SET/DELETE变慢命令,代表在操作bigkey

  • latency monitor工具

解决措施:

  • 用其他高效命令替代,例如使用SCAN代替全量查询
  • 执行排序、交集、并集等操作是,建议不使用SORT、SUNION、SINTER,可以在代码中处理

不建议在线上使用Keys命令,因为KEYS命令需要遍历存储的键值对,所以操作延时高

【额外】:keys命令的替代方案:

SCAN命令代替(也支持正则匹配),SCAN $cursor [match "[searchcontent]"] COUNT $count 命令会返回下一个游标,依次遍历会将所有key都查询出来,用该方式不会存在漏key,但是可能会存在返回多次同一个key

1、为什么不会漏key?Redis在SCAN遍历全局哈希表时,采用*高位进位法*的方式遍历哈希桶(可网上查询图例,一看就明白),当哈希表扩容后,通过这种算法遍历,旧哈希表中的数据映射到新哈希表,依旧会保留原来的先后顺序,这样就可以保证遍历时不会遗漏也不会重复。

2、为什么SCAN会得到重复的key?这个情况主要发生在哈希表缩容。已经遍历过的哈希桶在缩容时,会映射到新哈希表没有遍历到的位置,所以继续遍历就会对同一个key返回多次

针对Hash/Set/Sorted Set提供的HSCAN/SSCAN/ZSCAN命令:

针对key的SCAN和具体结构的SCAN再返回数量方面会有区别,针对Hash/Set/Sorted Set元素数量比较少时,底层会采用intset/ziplist方式存储,在执行HSCAN/SSCAN/ZSCAN命令时,会无视count参数,直接把所有元素一次性返回,也就是说,得到的元素数量是会大于count参数的。当底层转为哈希表或跳表存储时,才会真正使用发count参数,最多返回count个元素

2.1.2.过期key操作

过期key的自动删除机制:该机制会导致主线程阻塞,导致性能变慢

redis删除过期key的算法如下,默认是100ms执行一次:

(1)采样ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个数的key,将过期key删除

(2)如果步骤1超过25%的key过期了,则重复上述过程,直至过期key的占比低于25%

ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP是redis的一个参数,默认是20。正常1秒中内基本有200个key被删除,删除200个key并不会对主进程造成多大影响,但是可能会触发步骤2,重复执行删除操作,删除操作是阻塞的。而第二条触发的场景主要是在同一秒内大量的key同时过期

解决措施:

  • 根据业务场景,如果确实有一批key设置的过期时间参数是一致的,看是否允许存在一定大小范围内随机数的误差,允许的话就调整过期时间,这样就避免了1s内大量key同时过期,减少阻塞的风险

2.2.文件系统:AOF模式

AOF的三种写回策略,依赖文件系统的两个调用完成:write和fsync

write只需要将数据写到内核的数据缓冲区就可返回,并不需要等待日志实际写到磁盘;而fsync需要把日志写回磁盘才可返回,如下表:

img

写回策略everysec和always虽然都使用的fsync,但是还是有区别,由于everysec运行日志存在1s的丢失,因此采取子线程异步执行fsync(fsync执行较慢),主线程并不关心执行的结果,但是alaways的场景关注每一次的执行结果,因此不会采取异步子线程,而是主线程去执行fsync

由于AOF重写是交由fork出的子进程去执行,但是AOF重写这个过程会对磁盘进行大量的IO操作,同事fsync需要等到数据写到磁盘才会返回,因此这里也存在一个阻塞风险点:

虽然主线程不关系AOF子线程fsync写入的结果,但是会监控fsync的执行进度,当主线程下一次fsync发现上一次的fsync还未执行完成,这个时候就会阻塞(主线程)等到fsync完成。因此,如果后台子线程fsync频繁阻塞的换,主线程也会阻塞,导致redis性能变慢。

该过程如下图:

img

2.2.1.排查:

检查Redis配置文件中的appendfsync配置项:

img

  • 首先确实是否有必要使用always

  • 再确认是否对延迟敏感,但是对丢失数据不敏感,此时可将配置项no-appendfsync-on-rewrite设置为yes

    no-appendfsync-on-rewrite

    yes时,表示AOF重写时,不进行fsync操作。Redis实例将写请求放到缓存,不调用后台线程进行fsync操作,就可以直接返回。当然,如果此时实例发生宕机,则会导致数据丢失(AOF重写的是两处日志没有写入)

    no时。表示会调用后台线程进行fsync操作,此时会存在阻塞主线程的风险

2.2.2.解决措施

采用高速的固态,固态的带宽和并发度大约是机械硬盘的十倍,这个时候可以减少AOF重写和fsync后台线程并发带来的磁盘竞争从而阻塞主进程

2.3.操作系统:swap

**概念:**内存swap是内存和磁盘间来回换入换出的机制,涉及磁盘的读写。遇到swap(操心系统将 Redis 所用的内存分页移至 swap 空间时),无论换出还是换入的进程都收到慢速磁盘读写的影响。

Swap空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap空间中,等到那些程序要运行时,再从Swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行Swap交换Swap空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap空间中,等到那些程序要运行时,再从Swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行Swap交换

如下图:

image-20210420154107020

**对redis的影响:**当Redis和其他对内存需求大的应用一起运行时,redis是访问内存才能完成,一旦发生swap,redis的请求操作需要等待磁盘的读写操作执行完成才能继续,并且与fsync不同的是,swap影响的是redis的主线程

**swap发生的场景:**物理机器内存不足

发生原因:

  • redis实例占用了大量内存,导致物理机内存不足
  • 同一机器上的其他应用在进行大量的文件读写操作,操作系统的文件读写也会占用内存,导致分配给redis的内存减少,进而触发redis的swap

查看redis的swap情况:

  • 查找redis的进程号

  • 进入redis所在的proc目录/proc/pid

  • 运行命令:cat smaps | egrep '^(Swap|Size)'

    redis本身就有很多内存块,大小不一,不同内存块被换到磁盘上的大小也不一致,当出现百MB、GB级别的swap时,代表此时redis实例内存压力很大,很可能会变慢。

    $cat smaps | egrep '^(Swap|Size)'
    Size: 584 kB
    Swap: 0 kB
    Size: 4 kB
    Swap: 4 kB
    Size: 4 kB
    Swap: 0 kB
    Size: 462044 kB
    Swap: 462008 kB
    Size: 21392 kB
    Swap: 0 kB
    //其中size表示redis实例所使用的一块内存大小,swap代表换入到磁盘的内存大小
    

解决措施:

  • 最直接的就是增大内存
  • 使用切片集群,部署多个redis实例,分担内存的压力

2.4.操作系统:内存大页

Linux内核从2.6.38开始支持内存大页机制,该机制支持2MB大小的内存页分配,而常规的内存页分配是按4KB的粒度来执行的

实际的生产环境中不建议开启大页机制,因为redis在fork子进程生成快照以及重写AOF日志时,主线程仍然可以接收写请求,因为采取了写时复制的机制,如果开启了大页机制,则可能对100k的数据进行写操作,此时需要复制一页也就是2MB的内存,且此时会阻塞主进程

如何查看是否开启大页:

//如果执行结果是always,就表明内存大页机制被启动了;如果是never,就表示,内存大页机制被禁止。
cat /sys/kernel/mm/transparent_hugepage/enabled 

关闭:

echo never > /sys/kernel/mm/transparent_hugepage/enabled

为了使机器重启后 THP 配置依然生效
/etc/rc.local 中追加 echo never > /sys/kernel/mm/transparent_hugepage/enabled

3.检查checklist

  1. **检查基线性能:**检查redis的基线性能并与当前延迟进行比较

  2. **慢查询问题定位+解决:**慢查询问题的定位+解决(用scan等替代、聚合计算放在客户端)

    • 定位:a) 查看slowlog是否存在这些命令 b) Redis进程CPU使用率是否飙升(聚合运算命令导致)
    • 解决:a) 不使用复杂度过高的命令,或用其他方式代替实现(放在客户端做) b) 数据尽量分批查询(LRANGE key 0 N,建议N<=100,查询全量数据建议使用HSCAN/SSCAN/ZSCAN)
  3. **大量key同时过期:**是否对key设置了相同的过期时间,key的删除会阻塞主进程,在key上新增业务运行的浮动过期数值

    • 定位:a) 业务使用EXPIREAT/PEXPIREAT命令 b) Redis info中的expired_keys指标短期突增
    • 解决:a) 优化业务,过期增加随机时间,把时间打散,减轻删除过期key的压力 b) 运维层面,监控expired_keys指标,有短期突增及时报警排查
  4. **bigkey的操作:**redis 4.0则执行异步删除,4.0之前执行scan批量删除

    • 定位:a) slowlog出现很多SET/DELETE变慢命令(bigkey分配内存和释放内存变慢) b) 使用redis-cli -h $host -p $port --bigkeys扫描出很多bigkey
    • 解决:a) 优化业务,避免存储bigkey b) Redis 4.0+可开启lazy-free机制
  5. **AOF日志的写入策略:**避免AOF重写和fsync竞争磁盘IO资源,导致Redis延迟增加。根据业务设置no-appendfsync-on-rewrite为no,可能会丢失数据

  6. **Redis实例是否会产生Swap的风险:**定位+解决问题(扩大内存或切片集群)

    • 定位:a) 所有请求全部开始变慢 b) slowlog大量慢日志 c) 查看Redis进程是否使用到了Swap
    • 解决:a) 增加机器内存 b) 集群扩容 c) Swap使用时监控报警
  7. 是否启用大页机制:

    • 定位:查看是否启用
    • 解决:关闭
  8. **主从部署避免数据过大:**因为主从同步会导致从库加载大的RDB文件造成阻塞或数据丢失(同fork耗时过长)

  9. 是否使用了多核CPU或NUMA架构的机器运行Redis实例:

    • 定位:使用多核CPU时,可以给Redis实例绑定物理核;使用NUMA架构时,注意把Redis实例和网络中断处理程序运行在同一个CPU Socket上
    • 解决:a) Redis进程绑定多个CPU逻辑核 b) 网络中断处理程序和Redis进程绑定在同一个Socket下
  10. **fork耗时过长:**查看AOF重写和RDB快照的fork流程是否过长

    • 定位:a) Redis变慢只发生在生成RDB和AOF重写期间 b) 实例占用内存越大,fork拷贝内存页表越久 c) Redis info中latest_fork_usec耗时变长
    • 解决:a) 实例尽量小 b) Redis尽量部署在物理机上 c) 优化备份策略(例如低峰期备份) d) 合理配置repl-backlog和slave client-output-buffer-limit,避免主从全量同步 e) 视情况考虑关闭AOF f) 监控latest_fork_usec耗时是否变长
  11. Redis内存达到maxmemory

  • 定位:a) 实例内存达到maxmemory,且写入量大,淘汰key压力变大 b) Redis info中的evicted_keys指标短期突增
    • 解决:a) 业务层面,根据情况调整淘汰策略(随机比LRU快) b) 运维层面,监控evicted_keys指标,有短期突增及时报警 c) 集群扩容,多个实例减轻淘汰key的压力

内存淘汰策略在 Redis 4.0 之后有 8 种

  1. noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略;
  2. allkeys-lru:淘汰整个键值中最久未使用的键值;
  3. allkeys-random:随机淘汰任意键值;
  4. volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值;
  5. volatile-random:随机淘汰设置了过期时间的任意键值;
  6. volatile-ttl:优先淘汰更早过期的键值。

在 Redis 4.0 版本中又新增了 2 种淘汰策略:

  1. volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值;
  2. allkeys-lfu:淘汰整个键值中最少使用的键值。

其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。

我们可以根据实际的业务情况进行设置,默认的淘汰策略不淘汰任何数据,在新增时会报错。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈哈哈张大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值