背景
你是否有过这种困扰:我的数据量非常小,但还是报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会导致哪些问题?
-
Redis 无法写入,只能读取
image.png - 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