Redis 内存故障诊断及常用运维命令

背景

你是否有过这种困扰:我的数据量非常小,但还是报OOM错误?

# 一个简单set提示内存不足
[root@10-186-61-38 redis]# redis-cli -p 9999 set actionsky 1
(error) OOM command not allowed when used memory > 'maxmemory'.

首先我给大家解释下,Redis的OOM分两种

  • 一种是因Redis使用内存超出OS物理内存,OS将Redis进程杀死。
  • 另一种是Redis使用内存超过maxmemory参数配置,引发Redis Server层OOM。
    OOM是Redis最常见的内存故障,它影响很大:
  • 故障发生时,进程并不会退出,能读但无法写入
  • 配置了allkeys-lru、allkeys-lfu等内存淘汰策略场景下,会有大量键失效,导致缓存命中率急剧下降。
    本文中,我会给大家分享下该种内存问题的排查方向及运维命令

Redis内存消耗划分

image.png

简短介绍下Redis内存消耗划分情况,为下文诊断提供思路。上图可以总结Redis消耗内存分如下几块:

  • 对象内存:理论上占用最大,存储所有业务数据,如字符串类型、哈希类型对象等
  • 客户端内存:包括输入客户端(查询或写入命令)、输出客户端使用的内存,因为不受maxmemory参数控制,这块我们需重点排查
  • 复制积压缓冲:所有从库客户端共享、保存固定大小的写入命令用于从库失连后数据补偿
  • Redis自身内存:存储数据元数据信息、过期键字典等
  • AOF缓冲区:AOF持久化、重写缓冲区,一般占用很少,基本不需要关注

内存OOM会导致哪些问题?

  1. Redis 无法写入,只能读取


    image.png
  2. Redis 大量键被逐出内存或过期,导致Redis查询效率降低(maxmemory-policy配置为非默认值noeviction时)
    image.png

排查思路

注意:下文不做特别说明的话,我的maxmemory设置为1G,其它任何参数为默认

是否数据量太大?

使用redis-benchmark持续灌入数据

image.png

检查内存使用情况,发生OOM状态时 used_memory 一定会大于 maxmemory
image.png

检查数据对象内存和其它内存使用情况如下图:
image.png

这里有必要说明下overhead.tatal,它包括除数据外Redis消耗的所有内存,比如前面提到的复制缓冲区、客户端输入输出缓冲区等,另外还包括一些元数据如overhead.hashtable,它是数据库中元数据消耗的内存大小,包括以下三项:

  • 整个数据库是一种hash表,首先就是这张hash表使用的内存
  • 每一个key-value对都有一个dictEntry来记录他们的关系,元信息便包含该db中所有dictEntry使用的内存
  • redis使用redisObject来描述value所对应的不同数据类型(string、list、hash、set、zset),那么redisObject占用的空间也计算在元数据
    大家对这个现象可能有点疑惑,为啥我明明设置maxmemory为1G,你Redis只给我存了990多M数据就满了?
    很好理解,根据上面测试可知数据达到一定规模后,因需消耗额外的元数据、缓存内存,Redis最终将超过maxmemory而OOM。

是否客户端输入缓冲区有问题?

制造输入缓冲区压力(防止干扰,先清空数据再压测)


image.png
# 关键参数解释
-d 表示每个set值的大小,单位为字节
-c 启多少个连接

压测几秒钟后,触发OOM


image.png

检查输入缓冲区内存消耗,能看到客户端输入缓冲区消耗总量为 2.4G左右,远远超过maxmemory参数设置。


image.png

那我如何找到消耗内存量最大的那个连接呢?
image.png

可通过运行上述检查命令,定位到各客户端输入缓冲区的内存消耗(由大到小排序)。
一般如果定位到有连接异常,可以使用如下命令杀掉

# 例如杀掉上图中 id=51421 的连接
127.0.0.1:9999> CLIENT KILL ID 51421
(integer) 1

是否复制积压缓冲区有问题?

为测试方便,我直接把复制积压缓冲区配置为800M。
开启redis-benchmark压测进程


image.png

检查复制积压缓冲区内存消耗,可以看到因为缓冲区设置过大,数据量才存储190多M,Redis就无法写入了。


image.png

是否客户端输出缓冲区有问题?

若客户端输出缓冲区太大如何排查?一般该场景比较少见,常见于用到了redis的monitor命令

注意:monitor 命令功能像MySQL的general-log,能打印Redis所有执行的命令。在生产环境极少使用或禁用

先开启monitor命令

image.png

通过redis-benchmark制造输出缓冲区压力。
image.png

测试一段时间后观察Redis内存消耗
image.png

此时数据库无法写入
image.png

检查输出缓冲区各客户端连接内存消耗、输出缓冲区总消耗内存如下
image.png

可以看到输出缓冲区总内存已远大于maxmemory限制,此时内存自然就OMM。

实用命令

上文排查过程有些Redis运维命令我认为比较实用,整理如下

模拟Redis压力相关命令

# 1. 持续给Redis灌数据
redis-benchmark -p 9999 -t set -r 100000000 -l
# 2. 模拟输入缓冲区过大
redis-benchmark -p 9999 -q -c 10 -d 102400000 -n 10000000 -r 50000 -t set
# 3. 模拟输出缓冲区过大
redis-benchmark -p 9999 -t get -r 5000000 -n 10000000 -d 100 -c 1000 -P 500 -l

常用Redis内存排查命令

# 1. 快速查看Redis内存是否够用
redis-cli -p 9999 info memory |egrep '(used_memory_human|maxmemory_human|maxmemory_policy)'
# 2. 检查复制积压缓冲区使用情况
redis-cli -p 9999 memory stats|egrep -A 1 '(total.allocated|replication.backlog)'
# 3. 检查客户端输入缓冲区内存使用总量
redis-cli -p 9999 client list| awk 'BEGIN{sum=0} {sum+=substr($12,6);sum+=substr($13,11)}END{print sum}'
# 4. 检查客户端输入缓冲区各客户端连接的内存情况
redis-cli -p 9999 client list|awk '{print substr($12,6),$1,$12,$18}'|sort -nrk1,1 | cut -f1 -d" " --complement
# 5. 检查客户端输出缓冲区内存使用总量
redis-cli -p 9999 client list| awk 'BEGIN{sum=0} {sum+=substr($16,6)}END{print sum}'
# 6. 检查客户端输出缓冲区各客户端连接的内存使用排序
redis-cli -p 9999 client list|awk '{print substr($16,6),$1,$16,$18}'|sort -nrk1,1 | cut -f1 -d" " --complement |head -n10
# 7. 检查数据对象使用内存总量
redis-cli -p 9999 memory stats|grep -A 1 'dataset.bytes'

总结

  • Redis内存问题大部分可以通过上诉排查思路进行定位

  • 整理了一些常用Redis内存排查命令

  • 整理了一份Redis内存检查脚本

#!/bin/bash
# Author:Renzy
# ModifyDate:20210408

if [ $# -ne 1 ];then
    echo -e "\033[31mERROR\033[0m: A port must be provided."
    echo "eg: sh $0 6379"
    exit 1
fi

PORT=$1
CLI_BIN=/data/redis/bin/redis-cli
EXEC="$CLI_BIN -p $PORT "

# Define checking memory available.
checkMem(){
    USED_MEM=`$EXEC memory stats |grep -A 1 total.allocated|tail -n1`
    MAX_MEM=`$EXEC info memory|grep -w maxmemory|awk -F ':' '{print $2}'`
    MEM_POL=`$EXEC info memory|grep -w maxmemory_policy|awk -F ':' '{print $2}'`
    DATA_USE=`$EXEC memory stats|grep -A 1 'dataset.bytes'|tail -n1`
    EXCEPT_DATA=`$EXEC memory stats|grep -A 1 'overhead.total'|tail -n1`
    REPL_USE=`$EXEC memory stats|grep -A 1 'replication.backlog'|tail -n1`
    INOUT_USE=`$EXEC client list| awk 'BEGIN{sum=0} {sum+=substr($12,6);sum+=substr($13,11)}END{print sum}'`
    OUTPUT_USE=`$EXEC client list| awk 'BEGIN{sum=0} {sum+=substr($16,6)}END{print sum}'`

    STATUS=`$EXEC set actionsky 1`
    if [ "$STATUS" = 'OK' ];then
        echo -e "Redis当前内存是否可用: \033\033[32m${STATUS}\033[0m"
    else
        echo -e "Redis当前内存是否可用: \033\033[31m${STATUS}\033[0m"
    fi
    echo "Redis当前内存淘汰策略: $MEM_POL"
    echo "Redis当前已使用的内存(byte): $USED_MEM"
    echo "Redis当前最大内限限制(byte): $MAX_MEM"
    echo "Redis当前数据对象已使用内存(byte): $DATA_USE"
    echo "Redis当前除数据外总内存消耗(byte): $EXCEPT_DATA"
    echo "Redis当前复制积压缓存区消耗(byte): $REPL_USE"
    echo " "
    echo "Redis当前客户端输入缓存总消耗(byte): $INOUT_USE"
    echo "Redis当前客户端输入缓存各连接消耗(TOP10):"
    $EXEC client list|awk '{print substr($12,6),$1,$12,$18}'|sort -nrk1,1 | cut -f1 -d" " --complement
    echo " "
    echo "Redis当前客户端输出缓存总消耗(byte): $OUTPUT_USE"
    echo "Redis当前客户端输出缓存各连接消耗(TOP10):"
    $EXEC client list|awk '{print substr($16,6),$1,$16,$18}'|sort -nrk1,1 | cut -f1 -d" " --complement |head -n10
}

checkMem

脚本执行效果:


image.png

image.png

你可能感兴趣的:(Redis 内存故障诊断及常用运维命令)