Redis 4.x系列(二十四):Redis 内存与崩溃问题的故障诊断
Redis 是基于内存的键值数据库,除了延迟问题之外,内存使用是另一种致命问题。
在极少数情况下,Redis 可能遇到致命的崩溃问题,可能通过更多的操作来对问题进行诊断分析。
Redis Memory Optimization,译文:内存优化, Redis 4.x系列(十二):Redis 内存优化(使用合适的数据类型)
内存问题
Redis 内存最常见的问题有OOM(out-of-memory:内存溢出)
问题和内存碎片问题。Redis 基于内存存储的应用,内存不像硬盘那么大,是有限的,不正确的操作会导致内存问题,以致 Redis 读写出现故障。
Redis内存结构
内存问题分析
关注客户端查询缓冲区的增长情况
注意used_memory_human是否大于maxmemory_human1
2
3# ./redis-cli info memory|egrep "used_memory_human|maxmemory_human"
used_memory_human:9.51M
maxmemory_human:2.00Gused_memory包含了客户端查询缓冲区,会随客户端查询缓存区的增长而扩展。如果查询缓冲区异常增长,used_memory可能会比maxmemory大的多。此情况下,若配置了正确的内存策略,Redis 会开始淘汰键。
另外值注意的是,监控客户端输出缓冲区不包含在 INFO MEMORY 命令返回的 used_memory_overhead 指标中。但包含中** used_memory 指标中。因此,即使 Redis 实例中没有键,如果客户端输出缓冲区**被滥用,仍有可能导致
OOM
问题发生。Redis 如何判断是否可能发生
OOM
问题:used_memory - AOF_buffer_size - slave_output_buffer_size >= maxmemory
used_memory_overhead 计算公式:
used_memory_overhead = server_initial_memory_usage + repl_backlog + slave_clients_output_buffer + normal_clients_output_buffer + pubsub_clients_output_buffer + normal_clients_query_buffer + clients_metadata + AOF buffer + AOF rewrite buffer.
判断是否已经产生** OOM **问题,可向 Redis 实例中写入键值来验证是否正常工作
如果发生了OOM
问题,Redis 会根据淘汰策略来淘汰某些键。如果淘汰策略配置的是maxmemory_policy:noeviction
,则新的写入请求被拒绝并返回OOM
错误信息到客户端。1
2127.0.0.1:6379> set foo bar
(error) OOM command not allowed when used memory > 'maxmemory'注意** evicted_keys 指标增长的情况
该指标收集OOM问题后根据配置的淘汰策略而淘汰的键的数量,如果大于零,可能出现了OOM**问题。1
2# ./redis-cli -a 123456 info stats|grep evicted_keys
evicted_keys:0检查 Redis 实例存储的数据集大小是否超过了配置的 maxmemory 选项
查看数据集大小和占比,若 used_memory_dataset_perc 大于 **90%**,则可能是大数据造成的内存问题。1
2
3# ./redis-cli -a 123456 info memory|egrep "used_memory_dataset|used_memory_dataset_perc"
used_memory_dataset:2991896 //数据占用的内存大小(used_memory - used_memory_overhead)
used_memory_dataset_perc:72.02% //数据占用的内存大小的百分比(used_memory_dataset/(used_memory - used_memory_startup)) * 100%使用
--bigkeys
选项查巨大键1
./redis-cli -a 123456 --bigkeys
或使用 redis-rdb-tools工具生成内存使用情况的报告:
1
2
3
4
5
6
7
8
9
10
11
12# git clone https://github.com/sripathikrishnan/redis-rdb-tools.git
# cd redis-rdb-tools/
//安装
# python setup.py install
//进入Redis bin
# cd /usr/local/redis/bin/
// dump.rdb 所在目录生成 memory.csv 文件
# rdb -c memory dump.rdb --bytes 128 -f memory.csv
# sort -t, -k4nr memory.csv | more
0,list,user_list,189,quicklist,6,5,
0,hash,phone_num,139,ziplist,3,29,
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry检查 redis-server 进程的驻留集大小(RSS),判断是否占用了太多的内存而导致操作系统开始使用交换空间,甚至启动Linux OOM Killer来终止进程
系统内存和交换空间使用情况1
2
3
4# ./redis-cli -a 123456 info|grep process_id
process_id:34966
# awk '/VmSwap/{print $2 " " $3}' /proc/34966/status
0 kB还需要检查是否存在不正常的内部内存使用导致了内存问题(缓冲区溢出)
特别需要留意:used_memory_human, used_memory_dataset, used_memory_overhead 和 used_memory_dataset_perc指标。使用 redis-benchmark 工具模拟查询缓冲区导致的内存问题
1
2
3
4
5
6
7
8
9
10
11
12
13//测试环境分配的内存总量是
maxmemory_human:512.00M
//模拟 -n 10000000 个请求,每个键负载 -d 100 M 数据, 随机生成 -r 25000 keys, -t 尽做测试
# ./redis-benchmark -a 123456 -q -d 104857600 -n 10000000 -r 25000 -t set
//检查客户端查询缓冲区的内存占用情况
# ./redis-cli -a 123456 client list | awk 'BEGIN{sum=0} {sum+=substr($12,6);sum+=substr($13,11)}END{print sum}'
512032868
//设置值
# ./redis-cli -a 123456 set foo4 bar4
(error) OOM command not allowed when used memory > 'maxmemory'.若used_memory_human比maxmemory大得多,而used_memory_dataset_perc指标很小,则可能是客户端缓冲区导致OOM问题,无法再定改数据。
获取输出缓冲区排前 10 的客户端
监控客户端输出缓冲区也有可能产生巨大的内存开销,并且这些开销指标不包含在 used_memory_dataset_perc中(Redis v4.0.1)。1
# ./redis-cli -a 123456 client list | awk '{print substr($16,6),$1,$16,$18}'|sort -nrk1,1|cut -f1 -d" " --complement | head -n10
判断是不内存碎片开销导致的问题
内存碎片率计算:used_memory_rss / used_memory,若大于 1.5 说明碎片过多,利用率低,可执行memory purge
命令释放内存碎片。1
2
3
4# ./redis-cli -a 123456 info memory|grep mem_fragmentation_ratio
mem_fragmentation_ratio:9.21
127.0.0.1:6379>memory purge
崩溃问题
Redis 在极端情况下,可能会崩溃。当崩溃时,需要做些操作来诊断问题,并降低损失。
- 可以使用DEBUG SEGFAULT命令模拟 Redis 实例的崩溃:
1
2
3# ./redis-server redis.conf
# ./redis-cli -a 123456 debug segfault
Error: Server closed the connection - 再查看 Redis 日志,日崩溃相关信息,包括栈回溯、INFO命令的输出、客户端信息及寄存器都会记录到日志里。
- 若崩溃问题可以复现,在测试环境可使用 Linux 调试工具 GDB 来调试** redis-server ** 进程,帮助了解发生崩溃时的情形。
1
2
3
4
5
6
7
8
9
10# ./redis-cli -a 123456 info | grep process_id
process_id:55738
//安装GDB
# yum install gdb
//跟踪调试
# gdb redis-server 55738
(gdb) continue
Continuing. - 另起客户端连接,使用命令模拟崩溃,GDB 会打印出错误日志
1
2
3Program received signal SIGSEGV, Segmentation fault.
debugCommand (c=0x7f8c17162fc0) at debug.c:315
315 debug.c: No such file or directory. - 获取栈回溯和处理器的寄存信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(gdb) bt
#0 debugCommand (c=0x7f8c17162fc0) at debug.c:315
#1 0x000000000042c02e in call (c=c@entry=0x7f8c17162fc0, flags=flags@entry=15) at server.c:2229
#2 0x000000000042c737 in processCommand (c=0x7f8c17162fc0) at server.c:2515
#3 0x000000000043b825 in processInputBuffer (c=0x7f8c17162fc0) at networking.c:1357
#4 0x0000000000426770 in aeProcessEvents (eventLoop=eventLoop@entry=0x7f8c1703a050,
flags=flags@entry=11) at ae.c:443
#5 0x0000000000426a3b in aeMain (eventLoop=0x7f8c1703a050) at ae.c:501
#6 0x000000000042383f in main (argc=<optimized out>, argv=0x7fff3929e988) at server.c:3899
(gdb) info registers
rax 0x0 0
rbx 0x7f8c17162fc0 140239659478976
....
gs 0x0 0 - 检查变量值,并生成 core 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14(gdb) p /s (char*)c->argv[0]->ptr
$1 = 0x7f8c1701b073 "debug"
(gdb) p /s (char*)c->argv[1]->ptr
$2 = 0x7f8c1701b093 "segfault"
(gdb) p server
$3 = {pid = 55738, configfile = 0x7f8c1701c183 "/usr/local/redis/6379/redis.conf",
executable = 0x7f8c1701c153 "/usr/local/redis/6379/./redis-server", exec_argv = 0x7f8c17021d30,
hz = 10, db = 0x7f8c1714f000, commands = 0x7f8c17018060, orig_commands = 0x7f8c170180c0,
.....
(gdb) gcore
Saved corefile core.55738 - 最后,退出 GDB 如果对 Redis 原码熟悉的,可以根据日志修改问题重新编译,或发送 core 文件给 Redis 作者。
1
2
3
4
5
6
7(gdb) q
A debugging session is active.
Inferior 1 [process 55738] will be detached.
Quit anyway? (y or n) y
Detaching from program: /usr/local/redis/6379/redis-server, process 55738
相关参考
Redis 4.x系列(二十四):Redis 内存与崩溃问题的故障诊断
http://blog.gxitsky.com/2019/01/05/Redis-24-trouble-memory-crash-shoting/