vlambda博客
学习文章列表

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


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


二、内存使用优化


1.缩减键值对象

    

在条件允许的情况下建议字符串长度控制在39字节以内,减少创建redisObject内存分配次数;

    

缩减键(key)和值(value)的长度;

   

key长度:如在设计键时,在完整描述业务情况下,键值越短越好。

    

value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。在内存紧张的情况下,可以使用通用压缩算法压缩json,xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。

    

2.共享对象池

    

使用共享对象池。整数对象池在Redis中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount 命令查看对象引用数验证是否启用整数对象池技术。使用共享对象池后,相同的数据内存使用降低30%以上。可见当数据大量使用[0-9999]的整数时,共享对象池可以节约大量内存。注意,当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。

    

3.字符串优化

    

尽量减少字符串频繁修改操作如append,setrange, 改为直接使用set修改字符串,降低预分配带来的内存浪费和内存碎片化。

    

4.编码优化

    

针对性能要求较高的场景使用ziplist,建议长度不要超过1000,每个元素大小控制在512字节以内。

    

当集合只包含整数且长度不超过set-max-intset-entries配置时使用intset编码。

    

对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。


原文链接:https://blog.csdn.net/lxbjkben/java/article/details/83028493


精彩推荐







扫下方二维码关注“程序员考拉”,每日推荐优秀好文!




如果感觉推送内容不错,不妨右下角点个在看,感谢支持!