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"
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 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 ?