Redis 是基于内存的键值数据库,除了延迟问题之外,内存使用是另一种致命问题。
在极少数情况下,Redis 可能遇到致命的崩溃问题,可能通过更多的操作来对问题进行诊断分析。
Redis Memory Optimization,译文:内存优化, Redis 4.x系列(十二):Redis 内存优化(使用合适的数据类型)
内存问题
Redis 内存最常见的问题有OOM(out-of-memory:内存溢出)
问题和内存碎片问题。Redis 基于内存存储的应用,内存不像硬盘那么大,是有限的,不正确的操作会导致内存问题,以致 Redis 读写出现故障。
Redis内存结构
内存问题分析
关注客户端查询缓冲区的增长情况
注意used_memory_human是否大于maxmemory_human# ./redis-cli info memory|egrep "used_memory_human|maxmemory_human" used_memory_human:9.51M maxmemory_human:2.00G
used_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
错误信息到客户端。127.0.0.1:6379> set foo bar (error) OOM command not allowed when used memory > 'maxmemory'
注意 evicted_keys 指标增长的情况
该指标收集OOM问题后根据配置的淘汰策略而淘汰的键的数量,如果大于零,可能出现了OOM问题。# ./redis-cli -a 123456 info stats|grep evicted_keys evicted_keys:0
检查 Redis 实例存储的数据集大小是否超过了配置的 maxmemory 选项
查看数据集大小和占比,若 used_memory_dataset_perc 大于 90%,则可能是大数据造成的内存问题。# ./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
选项查巨大键./redis-cli -a 123456 --bigkeys
或使用 redis-rdb-tools工具生成内存使用情况的报告:
# 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来终止进程
系统内存和交换空间使用情况# ./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 工具模拟查询缓冲区导致的内存问题
//测试环境分配的内存总量是 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)。# ./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
命令释放内存碎片。# ./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 实例的崩溃:
# ./redis-server redis.conf # ./redis-cli -a 123456 debug segfault Error: Server closed the connection
再查看 Redis 日志,日崩溃相关信息,包括栈回溯、INFO命令的输出、客户端信息及寄存器都会记录到日志里。
若崩溃问题可以复现,在测试环境可使用 Linux 调试工具 GDB 来调试 redis-server 进程,帮助了解发生崩溃时的情形。
# ./redis-cli -a 123456 info | grep process_id process_id:55738 //安装GDB # yum install gdb //跟踪调试 # gdb redis-server 55738 (gdb) continue Continuing.
另起客户端连接,使用命令模拟崩溃,GDB 会打印出错误日志
Program received signal SIGSEGV, Segmentation fault. debugCommand (c=0x7f8c17162fc0) at debug.c:315 315 debug.c: No such file or directory.
获取栈回溯和处理器的寄存信息
(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 文件
(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
(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 原码熟悉的,可以根据日志修改问题重新编译,或发送 core 文件给 Redis 作者。
相关参考
注意:本文归作者所有,未经作者允许,不得转载