Redis 4.x系列(二十四):Redis 内存与崩溃问题的故障诊断

  Redis 是基于内存的键值数据库,除了延迟问题之外,内存使用是另一种致命问题。

  在极少数情况下,Redis 可能遇到致命的崩溃问题,可能通过更多的操作来对问题进行诊断分析。

  Redis Memory Optimization译文:内存优化, Redis 4.x系列(十二):Redis 内存优化(使用合适的数据类型)

内存问题

Redis 内存最常见的问题有OOM(out-of-memory:内存溢出)问题和内存碎片问题。Redis 基于内存存储的应用,内存不像硬盘那么大,是有限的,不正确的操作会导致内存问题,以致 Redis 读写出现故障。

Redis内存结构

Redis 内存结构

内存问题分析

  1. 关注客户端查询缓冲区的增长情况
    注意used_memory_human是否大于maxmemory_human

    1
    2
    3
    # ./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.

  2. 判断是否已经产生** OOM **问题,可向 Redis 实例中写入键值来验证是否正常工作
    如果发生了OOM问题,Redis 会根据淘汰策略来淘汰某些键。如果淘汰策略配置的是maxmemory_policy:noeviction,则新的写入请求被拒绝并返回OOM错误信息到客户端。

    1
    2
    127.0.0.1:6379> set foo bar
    (error) OOM command not allowed when used memory > 'maxmemory'
  3. 注意** evicted_keys 指标增长的情况
    该指标收集
    OOM问题后根据配置的淘汰策略而淘汰的键的数量,如果大于零,可能出现了OOM**问题。

    1
    2
    # ./redis-cli -a 123456 info stats|grep evicted_keys
    evicted_keys:0
  4. 检查 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
  5. 检查 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
  6. 还需要检查是否存在不正常的内部内存使用导致了内存问题(缓冲区溢出)
    特别需要留意:used_memory_human, used_memory_dataset, used_memory_overheadused_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_humanmaxmemory大得多,而used_memory_dataset_perc指标很小,则可能是客户端缓冲区导致OOM问题,无法再定改数据。

  7. 获取输出缓冲区排前 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
  8. 判断是不内存碎片开销导致的问题
    内存碎片率计算: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 在极端情况下,可能会崩溃。当崩溃时,需要做些操作来诊断问题,并降低损失。

  1. 可以使用DEBUG SEGFAULT命令模拟 Redis 实例的崩溃:
    1
    2
    3
    # ./redis-server redis.conf 
    # ./redis-cli -a 123456 debug segfault
    Error: Server closed the connection
  2. 再查看 Redis 日志,日崩溃相关信息,包括栈回溯、INFO命令的输出、客户端信息及寄存器都会记录到日志里。
  3. 若崩溃问题可以复现,在测试环境可使用 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.
  4. 另起客户端连接,使用命令模拟崩溃,GDB 会打印出错误日志
    1
    2
    3
    Program received signal SIGSEGV, Segmentation fault.
    debugCommand (c=0x7f8c17162fc0) at debug.c:315
    315 debug.c: No such file or directory.
  5. 获取栈回溯和处理器的寄存信息
    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
  6. 检查变量值,并生成 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
  7. 最后,退出 GDB
    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 原码熟悉的,可以根据日志修改问题重新编译,或发送 core 文件给 Redis 作者。

相关参考

  1. Redis debugging guide
  2. GDB: The GNU Project Debugger
  3. Redis RDB 快照文件分析工具 redis-rdb-tools
  4. Redis 内存分析工具 redis-memory-analyzer
  5. Redis 内存碎片整理
  6. sysbench 压力测试工具

Redis 4.x系列(二十四):Redis 内存与崩溃问题的故障诊断

http://blog.gxitsky.com/2019/01/05/Redis-24-trouble-memory-crash-shoting/

作者

光星

发布于

2019-01-05

更新于

2022-08-14

许可协议

评论