一. 內(nèi)存分配控制
1. vm.overcommit_memory
Redis在啟動(dòng)時(shí)可能會(huì)出現(xiàn)這樣的日志:
# WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the
command 'sysctl vm.overcommit_memory=1' for this to take effect.
在分析這個(gè)問(wèn)題之前,首先要弄清楚什么是overcommit?Linux操作系統(tǒng)對(duì)大部分申請(qǐng)內(nèi)存的請(qǐng)求都回復(fù)yes,以便能運(yùn)行更多的程序。因?yàn)樯暾?qǐng)內(nèi)存后,并不會(huì)馬上使用內(nèi)存,這種技術(shù)叫做overcommit。如果Redis在啟動(dòng)時(shí)有上面的日志,說(shuō)明vm.overcommit_memory=0,Redis提示把它設(shè)置為1。
vm.overcommit_memory用來(lái)設(shè)置內(nèi)存分配策略,它有三個(gè)可選值,如下表所示。
| vm.overcommit_memory | 含義 |
|---|---|
| 0 | 表示內(nèi)核將檢查是否有足夠的可用內(nèi)存。如果有足夠的可用內(nèi)存,內(nèi)存申請(qǐng)通過(guò),否則內(nèi)存申請(qǐng)失敗,并把錯(cuò)誤返回給應(yīng)用進(jìn)程 |
| 1 | 表示內(nèi)核允許超量使用內(nèi)存直到用完為止 |
| 2 | 表示內(nèi)核決不過(guò)量的("never overcommit")使用內(nèi)存,即系統(tǒng)整個(gè)內(nèi)存地址空間不能超過(guò)swap+50%的RAM值,50%是overcommit_ratio默認(rèn)值,此參數(shù)同樣支持修改 |
注意:本文的可用內(nèi)存代表物理內(nèi)存與swap之和。
日志中的Background save代表的是bgsave和bgrewriteaof,如果當(dāng)前可用內(nèi)存不足,操作系統(tǒng)應(yīng)該如何處理fork。如果vm.overcommit_memory=0,代表如果沒(méi)有可用內(nèi)存,就申請(qǐng)內(nèi)存失敗,對(duì)應(yīng)到Redis就是fork執(zhí)行失敗,在Redis的日志會(huì)出現(xiàn):
Cannot allocate memory
Redis建議把這個(gè)值設(shè)置為1,是為了讓fork能夠在低內(nèi)存下也執(zhí)行成功。
2. 獲取和設(shè)置
cat /proc/sys/vm/overcommit_memory
0
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
3. 最佳實(shí)踐
Redis設(shè)置合理的maxmemory,保證機(jī)器有20%~30%的閑置內(nèi)存。
集中化管理aof重寫和rdb的bgsave。
設(shè)置vm.overcommit_memory=1,防止極端情況下,會(huì)造成fork失敗。
二. swappiness
1. 參數(shù)說(shuō)明
swap對(duì)于操作系統(tǒng)來(lái)比較重要,當(dāng)物理內(nèi)存不足時(shí),可以swap out一部分內(nèi)存頁(yè),以解燃眉之急。但世界上沒(méi)有免費(fèi)午餐,swap空間由硬盤提供,對(duì)于需要高并發(fā)、高吞吐的應(yīng)用來(lái)說(shuō),磁盤IO通常會(huì)成為系統(tǒng)瓶頸。在Linux中,并不是要等到所有物理內(nèi)存都使用完才會(huì)使用到swap,系統(tǒng)參數(shù)swppiness會(huì)決定操作系統(tǒng)使用swap的傾向程度。swappiness的取值范圍是0~100,swappiness的值越大,說(shuō)明操作系統(tǒng)可能使用swap的概率越高,swappiness值越低,表示操作系統(tǒng)更加傾向于使用物理內(nèi)存。swap的默認(rèn)值是60,了解這個(gè)值的含義后,有利于Redis的性能優(yōu)化。下表對(duì)swappiness的重要值進(jìn)行了說(shuō)明。
| swapniess | 策略 |
|---|---|
| 0 | Linux3.5以及以上:寧愿OOM killer也不用swap;Linux3.4以及更早:寧愿swap也不要OOM killer |
| 1 | Linux3.5以及以上:寧愿swap也不要OOM killer |
| 60 | 默認(rèn)值 |
| 100 | 操作系統(tǒng)會(huì)主動(dòng)地使用swap |
運(yùn)維提示:OOM(Out Of Memory) killer機(jī)制是指Linux操作系統(tǒng)發(fā)現(xiàn)可用內(nèi)存不足時(shí),強(qiáng)制殺死一些用戶進(jìn)程(非內(nèi)核進(jìn)程),來(lái)保證系統(tǒng)有足夠的可用內(nèi)存進(jìn)行分配。
從下表中可以看出,swappiness參數(shù)在Linux 3.5版本前后的表現(xiàn)并不完全相同,Redis運(yùn)維人員在設(shè)置這個(gè)值需要關(guān)注當(dāng)前操作系統(tǒng)的內(nèi)核版本。
2. 設(shè)置方法
swappiness設(shè)置方法如下:
echo {bestvalue} > /proc/sys/vm/swappiness
#但是上述方法在系統(tǒng)重啟后就會(huì)失效,為了讓配置在重啟Linux操作系統(tǒng)后立即生效,只需要在/etc/sysctl.conf追加 vm.swappiness={bestvalue}即可。
echo vm.swappiness={bestvalue} >> /etc/sysctl.conf
需要注意/proc/sys/vm/swappiness是設(shè)置操作,/etc/sysctl.conf是追加操作。
3. 如何監(jiān)控swap
(1) 查看swap的總體情況
Linux提供了free命令來(lái)查詢操作系統(tǒng)的內(nèi)存使用情況,其中也包含了swap的相關(guān)使用情況。下面是某臺(tái)Linux服務(wù)器執(zhí)行free –m(以兆為到位)的結(jié)果,其中需要重點(diǎn)關(guān)注的是最后一行的swap統(tǒng)計(jì),從執(zhí)行結(jié)果看,swap一共有4095M,使用了0M,空閑了4095M。
total used free shared buffers cached
Mem: 64385 31573 32812 0 505 10026
-/+ buffers/cache: 21040 43344
Swap: 4095 0 4095
在另一臺(tái)Linux服務(wù)器同樣執(zhí)行free -m,這臺(tái)服務(wù)器開啟了8189M swap,其中使用了5241M。
total used free shared buffers cached
Mem: 24096 8237 15859 0 136 2483
-/+ buffers/cache: 5617 18479
Swap: 8189 5241 2947
(2) 實(shí)時(shí)查看swap的使用
Linux提供了vmstat命令查詢系統(tǒng)的相關(guān)性能指標(biāo),其中包含負(fù)載、CPU、內(nèi)存、swap、IO的相關(guān)屬性。但其中和swap有關(guān)的指標(biāo)是si和so,它們分別代表了操作系統(tǒng)的swap in和swap out。下面是執(zhí)行vmstat 1(每隔一秒輸出)的效果,可以看到si和so都為0,代表當(dāng)前沒(méi)有使用swap。
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 33593468 517656 10271928 0 0 0 1 0 0 8 0 91 0 0
4 0 0 33594516 517656 10271928 0 0 0 0 10606 9647 10 1 90 0 0
1 0 0 33594392 517656 10271928 0 0 0 0 11490 10244 11 1 89 0 0
6 0 0 33594292 517656 10271928 0 0 0 36 12406 10681 13 1 87 0 0
(3) 查看指定進(jìn)程的swap使用情況
Linux操作系統(tǒng)中,/proc/{pid}目錄是存儲(chǔ)指定進(jìn)程的相關(guān)信息,其中/proc/{pid}/smaps是記錄了當(dāng)前進(jìn)程所對(duì)應(yīng)的內(nèi)存映像信息,這個(gè)信息對(duì)于查詢指定進(jìn)程的swap使用情況很有幫助。下面以一個(gè)Redis實(shí)例進(jìn)行說(shuō)明 通過(guò)info server獲取Redis的進(jìn)程號(hào)process_id:
redis-cli -h ip -p port info server | grep process_id
process_id:986
通過(guò)cat proc/986/smaps查詢Redis的smaps信息,由于有多個(gè)內(nèi)存塊信息,這里只輸出一個(gè)內(nèi)存塊鏡像信息進(jìn)行觀察。
2aab0a400000-2aab35c00000 rw-p 2aab0a400000 00:00 0
Size: 712704 kB
Rss: 617872 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 15476 kB
Private_Dirty: 602396 kB
Swap: 58056 kB
Pss: 617872 kB
其中Swap字段代表該內(nèi)存塊存在swap分區(qū)的數(shù)據(jù)大小。通過(guò)執(zhí)行如下命令,就可以找到每個(gè)內(nèi)存塊鏡像信息中,這個(gè)進(jìn)程使用到的swap量,通過(guò)求和就可以算出總的swap用量。
cat /proc/986/smaps | grep Swap
Swap: 0 kB
Swap: 0 kB
…
Swap: 0 kB
Swap: 478320 kB
…
Swap: 624 kB
Swap: 0 kB
4. 最佳實(shí)踐
如果Linux>3.5,vm.swapniess=1,否則vm.swapniess=0,從而實(shí)現(xiàn)如下兩個(gè)目標(biāo):
物理內(nèi)存充足時(shí)候,使Redis足夠快。
物理內(nèi)存不足時(shí)候,避免Redis死掉(如果當(dāng)前Redis為高可用,死掉比阻塞更好)。
三. Transparent Huge Pages
Redis在啟動(dòng)時(shí)可能會(huì)看到如下日志:
WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
從提示看Redis建議修改Transparent Huge Pages (THP)的相關(guān)配置,Linux kernel在2.6.38內(nèi)核增加了Transparent Huge Pages (THP)特性 ,支持大內(nèi)存頁(yè)(2MB)分配,默認(rèn)開啟。當(dāng)開啟時(shí)可以降低fork子進(jìn)程的速度,但fork之后,每個(gè)內(nèi)存頁(yè)從原來(lái)4KB變?yōu)?MB,會(huì)大幅增加重寫期間父進(jìn)程內(nèi)存消耗。同時(shí)每次寫命令引起的復(fù)制內(nèi)存頁(yè)單位放大了512倍,會(huì)拖慢寫操作的執(zhí)行時(shí)間,導(dǎo)致大量寫操作慢查詢。例如簡(jiǎn)單的incr命令也會(huì)出現(xiàn)在慢查詢中。因此Redis日志中建議將此特性進(jìn)行禁用,禁用方法如下:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
而且為了使機(jī)器重啟后THP配置依然生效,可以在/etc/rc.local中追加echo never > /sys/kernel/mm/transparent_hugepage/enabled。
echo "if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
echo never > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi">> /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local
在設(shè)置THP配置時(shí)需要注意:有些Linux的發(fā)行版本沒(méi)有將THP放到/sys/kernel/mm/transparent_hugepage/enabled中,例如Red Hat 6以上的THP配置放到/sys/kernel/mm/redhat_transparent_hugepage/enabled中。而Redis源碼中檢查THP時(shí),把THP位置寫死。
FILE *fp = fopen("/sys/kernel/mm/transparent_hugepage/enabled","r");
if (!fp) return 0;
所以在發(fā)行版中,雖然沒(méi)有THP的日志提示,但是依然存在THP所帶來(lái)的問(wèn)題。
echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
四. OOM killer
OOM killer會(huì)在可用內(nèi)存不足時(shí)選擇性的殺掉用戶進(jìn)程,它的運(yùn)行規(guī)則是怎樣的,會(huì)選擇哪些用戶進(jìn)程“下手”呢?OOM killer進(jìn)程會(huì)為每個(gè)用戶進(jìn)程設(shè)置一個(gè)權(quán)值,這個(gè)權(quán)值越高,被“下手”的概率就越高,反之概率越低。每個(gè)進(jìn)程的權(quán)值存放在/proc/{progress_id}/oom_score中,這個(gè)值是受/proc/{progress_id}/oom_adj的控制,oom_adj在不同的Linux版本的最小值不同,可以參考Linux源碼中oom.h(從-15到-17)。當(dāng)oom_adj設(shè)置為最小值時(shí),該進(jìn)程將不會(huì)被OOM killer殺掉,設(shè)置方法如下。
echo {value} > /proc/${process_id}/oom_adj
對(duì)于Redis所在的服務(wù)器來(lái)說(shuō),可以將所有Redis的oom_adj設(shè)置為最低值或者稍小的值,降低被OOM killer殺掉的概率。
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done
運(yùn)維提示:
有關(guān)OOM killer的詳細(xì)細(xì)節(jié),可以參考Linux源碼mm/oom_kill.c中oom_badness函數(shù)。
筆者認(rèn)為oom_adj參數(shù)只能起到輔助作用,合理的規(guī)劃內(nèi)存更為重要。
通常在高可用情況下,被殺掉比僵死更好,因此不要過(guò)多依賴oom_adj配置
| adj級(jí)別 | 值 | 說(shuō)明 |
|---|---|---|
| UNKNOWN_ADJ | 16 | 預(yù)留的最低級(jí)別,一般對(duì)于緩存的進(jìn)程才有可能設(shè)置成這個(gè)級(jí)別 |
| CACHED_APP_MAX_ADJ | 15 | 緩存進(jìn)程,空進(jìn)程,在內(nèi)存不足的情況下就會(huì)優(yōu)先被kill |
| CACHED_APP_MIN_ADJ | 9 | 緩存進(jìn)程,也就是空進(jìn)程 |
| SERVICE_B_ADJ | 8 | 不活躍的進(jìn)程 |
| PREVIOUS_APP_ADJ | 7 | 切換進(jìn)程 |
| HOME_APP_ADJ | 6 | 與Home交互的進(jìn)程 |
| SERVICE_ADJ | 5 | 有Service的進(jìn)程 |
| HEAVY_WEIGHT_APP_ADJ | 4 | 高權(quán)重進(jìn)程 |
| BACKUP_APP_ADJ | 3 | 正在備份的進(jìn)程 |
| PERCEPTIBLE_APP_ADJ | 2 | 可感知的進(jìn)程,比如那種播放音樂(lè) |
| VISIBLE_APP_ADJ | 1 | 可見(jiàn)進(jìn)程 |
| FOREGROUND_APP_ADJ | 0 | 前臺(tái)進(jìn)程 |
| PERSISTENT_SERVICE_ADJ | -11 | 重要進(jìn)程 |
| PERSISTENT_PROC_ADJ | -12 | 核心進(jìn)程 |
| SYSTEM_ADJ | -16 | 系統(tǒng)進(jìn)程 |
| NATIVE_ADJ | -17 | 系統(tǒng)起的Native進(jìn)程 |
五. 使用NTP保證Redis服務(wù)器時(shí)鐘的一致性
NTP(Network Time Protocol)網(wǎng)絡(luò)時(shí)間協(xié)議,一種保證不同機(jī)器時(shí)鐘一致性的服務(wù)。我們知道像Redis Sentinel和Redis Cluster這兩種需要多個(gè)Redis實(shí)例的類型,可能會(huì)涉及多臺(tái)服務(wù)器。雖然Redis并沒(méi)有對(duì)多個(gè)服務(wù)器的時(shí)鐘有嚴(yán)格的要求,但是假如多個(gè)Redis實(shí)例所在的服務(wù)器時(shí)鐘不一致,對(duì)于一些異常情況的日志排查是非常困難的,例如Redis Cluster的故障轉(zhuǎn)移,如果日志時(shí)間不一致,對(duì)于我們排查問(wèn)題帶來(lái)很大的困擾(注:但不會(huì)影響集群功能,集群節(jié)點(diǎn)依賴各自時(shí)鐘)。一般公司里都會(huì)有NTP服務(wù)用來(lái)提供標(biāo)準(zhǔn)時(shí)間服務(wù),從而達(dá)到糾正時(shí)鐘的效果(如下圖所示),為此我們可以每天定時(shí)去同步一次系統(tǒng)時(shí)間,從而使得集群中的時(shí)間是統(tǒng)一。

例如每小時(shí)的同步1次NTP服務(wù)
0 * * * * /usr/sbin/ntpdate ntp.xx.com > /dev/null 2>&1
六. ulimit
在Linux中,可以通過(guò)ulimit查看和設(shè)置系統(tǒng)的當(dāng)前用戶進(jìn)程的資源數(shù)。其中ulimit -a命令包含的open files參數(shù),是單個(gè)用戶同時(shí)打開的最大文件個(gè)數(shù)。
# ulimit –a
…
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
…
Redis允許同時(shí)有多個(gè)客戶端通過(guò)網(wǎng)絡(luò)進(jìn)行連接,可以通過(guò)配置maxclients來(lái)限制最大客戶端連接數(shù)。對(duì)Linux操作系統(tǒng)來(lái)說(shuō)這些網(wǎng)絡(luò)連接都是文件句柄。假設(shè)當(dāng)前open files是4096,那么啟動(dòng)Redis時(shí)會(huì)看到如下日志。
# You requested maxclients of 10000 requiring at least 10032 max file descriptors.
#第一行:Redis建議把open files至少設(shè)置成10032,那么這個(gè)10032是如何來(lái)的呢?因?yàn)閙axclients的默認(rèn)是10000,這些是用來(lái)處理客戶端連接的,除此之外,Redis內(nèi)部會(huì)使用最多32個(gè)文件描述符,所以這里的10032 = 10000 + 32。
# Redis can’t set maximum open files to 10032 because of OS error: Operation not permitted.
#第二行:Redis不能將open files設(shè)置成10032,因?yàn)樗鼪](méi)有權(quán)限設(shè)置。
# Current maximum open files is 4096. Maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase ‘ulimit –n’.
#第三行:當(dāng)前系統(tǒng)的open files是4096,所以maxclients被設(shè)置成4096-32=4064個(gè),如果你想設(shè)置更高的maxclients,請(qǐng)使用ulimit -n來(lái)設(shè)置。
從上面的三行日志分析可以看出open files的限制優(yōu)先級(jí)比maxclients大。open files的設(shè)置方法如下:
# 臨時(shí)
ulimit –Sn 10032
# 永久
tee etc/security/limits.conf <<'EOF'
* soft nofile 10032
* hard nofile 10032
* soft nproc 65535
* hard nproc 65535
EOF
七. TCP backlog 日志隊(duì)列優(yōu)化
Redis默認(rèn)的tcp-backlog為511,可以通過(guò)修改配置tcp-backlog進(jìn)行調(diào)整,如果Linux的tcp-backlog小于Redis設(shè)置的tcp-backlog,那么在Redis啟動(dòng)時(shí)會(huì)看到如下日志:
# WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
查看
cat /proc/sys/net/core/somaxconn
128
修改
echo 511 > /proc/sys/net/core/somaxconn
系統(tǒng)優(yōu)化配置
# - 設(shè)置內(nèi)存分配策略
sysctl -w vm.overcommit_memory=1
# - 盡量使用物理內(nèi)存(速度快)針對(duì)內(nèi)核版本大于>=3.x (寧愿swap也不要OOM killer)
sysctl -w vm.swapniess=1
# - 禁用 THP 特性減少內(nèi)存消耗
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# - OOM killer 特性優(yōu)化
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done
# - 設(shè)置其打開文件數(shù)句柄數(shù)以及單個(gè)用戶最大進(jìn)程數(shù)
tee etc/security/limits.conf <<'EOF'
* soft nofile 10032
* hard nofile 10032
* soft nproc 65535
* hard nproc 65535
EOF
# - SYN隊(duì)列長(zhǎng)度設(shè)置此參數(shù)可以容納更多等待連接的網(wǎng)絡(luò)。
echo 511 > /proc/sys/net/core/somaxconn
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=2048
# - 每個(gè)小時(shí)同步一次時(shí)間
0 * * * * /usr/sbin/ntpdate ntp.xx.com > /dev/null 2>&1
應(yīng)用配置優(yōu)化
# 最大客戶端上限連接數(shù)(需根據(jù)實(shí)際情況調(diào)整與系統(tǒng)的open files有關(guān),其數(shù)量值為open files(10032) - 32)
maxclients 10000
# 集群配置優(yōu)化關(guān)鍵項(xiàng)
# 集群超時(shí)時(shí)間,如果此時(shí)間設(shè)置太小時(shí)由于網(wǎng)絡(luò)波動(dòng)可能會(huì)導(dǎo)致進(jìn)行重新選Master的操作
cluster-node-timeout 5000
# 主節(jié)點(diǎn)寫入后必須同步到一臺(tái)從上,防止數(shù)據(jù)丟失的有效方法(要求是其從節(jié)點(diǎn)必須>=1)
min‐replicas‐to‐write 1
應(yīng)用使用中優(yōu)化
# (1) 查詢執(zhí)行時(shí)間指的是不包括像客戶端響應(yīng)(talking)、發(fā)送回復(fù)等 IO 操作,而單單是執(zhí)行一個(gè)查詢命令所耗費(fèi)的時(shí)間
redis> SLOWLOG LEN # 管理 redis 的慢日志查看當(dāng)前日志的數(shù)量
redis> SLOWLOG RESET # 清空 slowlog 此時(shí)上面 LEN 變成 0
# (2) 斷開耗時(shí)連接
# 列出所有已連接客戶端
redis 127.0.0.1:6379> CLIENT LIST
addr=127.0.0.1:43501 fd=5 age=10 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
# 殺死當(dāng)前客戶端的連接
redis 127.0.0.1:6379> CLIENT KILL 127.0.0.1:43501
OK