Redis进程异常退出排查
一、排查思路
1. 是否因为系统内存不足被oom killer杀掉;
如果是oom killer杀掉的话,一般会在/var/log/*留下日志,dmesg也应该能查到,可以使用命令搜索:
dmesg | egrep -i 'killed process'
grep oom /var/log/*
grep total_vm /var/log/*
如果确实是由于内存不足被oom killer杀掉,可以考虑:
- 加大系统内存;
- 修改redis.conf配置,设置合理的maxmemory,保证机器有20%~30%的闲置内存,并使用适当的回收策略;
maxmemory 512mb
maxmemory-policy volatile-lru
- 降低redis进程的oom adj分数;
“OOM killer会在可用内存不足时选择性的杀掉用户进程,它的运行规则是怎样的,会选择哪些用户进程“下手”呢?OOM killer进程会为每个用户进程设置一个权值,这个权值越高,被“下手”的概率就越高,反之概率越低。每个进程的权值存放在/proc/{progress_id}/oom_score中,这个值是受/proc/{progress_id}/oom_adj的控制,oom_adj在不同的Linux版本的最小值不同,可以参考Linux源码中oom.h(从-15到-17)。当oom_adj设置为最小值时,该进程将不会被OOM killer杀掉。”
设置方法如下:
echo -17 > /proc/${process_id}/oom_adj
- 优化redis的内存使用与设置;
详情参考下文的介绍以及文末的相关链接。
2. 是否被人为kill掉
- 如果是人为kill进程,可以搜索last和history的记录,查看是什么登陆用户在什么时间杀掉的;
- 如果系统有安装audit,也可以使用ausearch搜索命令记录:
ausearch -sc kill
3. 是否因为redis-cli发送了shutdown命令导致
如果是cli发送的shutdown命令导致redis退出,redis本身的log会有如下记录:
3803:S 11 Oct 18:12:51.251 # User requested shutdown...
3803:S 11 Oct 18:12:51.251 * Removing the pid file.
3803:S 11 Oct 18:12:51.252 # Redis is now ready to exit, bye bye...
如果还没打开redis log,需要在redis.conf中指定路径然后重启:
dir "/opt/redis-4.0.9/redis-cluster/7000"
避免进程被cli远程关闭,可以设置密码,或者在redis.conf中禁用shutdown命令来实现,我们选择采用后者:
rename-command SHUTDOWN ""
4. 是否redis本身的bug导致崩溃
- 设置系统ulimit,在进程崩溃时候生成core dump:
ulimit -c unlimited
修改/etc/sysctl.conf文件,加入如下配置指定core dump文件生成的名称和路径:
kernel.core_uses_pid = 1
kernel.core_pattern = /var/core/core_%e_%p
注意不同linux发行版可能设置的方式略有不同。
- 使用strace命令跟踪进程行为:
screen -S redis_trace_7000
strace -T -tt -e trace=all -p `ps -ef|grep redis|grep 7000|grep -v -i screen|grep -v grep|awk '{split($0,a);print a[2]}'`
然后ctrl-a d退出screen
- 使用gdb attach到redis进程
同样建议使用独立的screen执行gdb,另外,gdb需要使用ptrace,如果发现ptrace: Operation not permitted
的报错,需要关闭其他已经使用ptrace命令的进程,例如strace命令。
gdb /usr/local/bin/redis-server 58414
(gdb) continue
使用redis-cli连上server,发送debug segfault指令可以使redis server崩溃,这时候可以:
使用bt查看栈信息
(gdb) bt
#0 debugCommand (c=0x7ffc32005000) at debug.c:220
#1 0x000000010d246d63 in call (c=0x7ffc32005000) at redis.c:1163
#2 0x000000010d247290 in processCommand (c=0x7ffc32005000) at redis.c:1305
#3 0x000000010d251660 in processInputBuffer (c=0x7ffc32005000) at networking.c:959
#4 0x000000010d251872 in readQueryFromClient (el=0x0, fd=5, privdata=0x7fff76f1c0b0, mask=220924512) at networking.c:1021
#5 0x000000010d243523 in aeProcessEvents (eventLoop=0x7fff6ce408d0, flags=220829559) at ae.c:352
#6 0x000000010d24373b in aeMain (eventLoop=0x10d429ef0) at ae.c:397
......
使用info registers查看寄存器信息
(gdb) info registers
rax 0x0 0
rbx 0x7ffc32005000 140721147367424
rcx 0x10d2b0a60 4515891808
rdx 0x7fff76f1c0b0 140735188943024
rsi 0x10d299777 4515796855
rdi 0x0 0
rbp 0x7fff6ce40730 0x7fff6ce40730
rsp 0x7fff6ce40650 0x7fff6ce40650
r8 0x4f26b3f7 1327936503
r9 0x7fff6ce40718 140735020271384
r10 0x81 129
......
最后,使用gcore产生core dump文件
(gdb) gcore
Saved corefile core.58414
二、内存使用优化
- 缩减键值对象
在条件允许的情况下建议字符串长度控制在39字节以内,减少创建redisObject内存分配次数;
缩减键(key)和值(value)的长度;
key长度:如在设计键时,在完整描述业务情况下,键值越短越好。
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。在内存紧张的情况下,可以使用通用压缩算法压缩json,xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。 - 共享对象池
使用共享对象池。整数对象池在Redis中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount 命令查看对象引用数验证是否启用整数对象池技术。使用共享对象池后,相同的数据内存使用降低30%以上。可见当数据大量使用[0-9999]的整数时,共享对象池可以节约大量内存。注意,当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。 - 字符串优化
尽量减少字符串频繁修改操作如append,setrange, 改为直接使用set修改字符串,降低预分配带来的内存浪费和内存碎片化。 - 编码优化
针对性能要求较高的场景使用ziplist,建议长度不要超过1000,每个元素大小控制在512字节以内。
当集合只包含整数且长度不超过set-max-intset-entries配置时使用intset编码。
对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。
三、参考资料
设置Redis最大占用内存
Redis的内存优化
Redis禁用命令、危险命令及规避方法
Redis debugging guide
Playing with ptrace, Part I
神器strace, ltrace
How to disable Redis RDB?
Redis研究(十九)—命令属性
Virtual memory settings in Linux - The Problem with Overcommit
What is the logic behind killing processes during an Out of Memory situation?
How to reproduce a condition which invokes the OOM-Killer ?