源 / 程序員歷小兵 Redis 是一種內存數(shù)據(jù)庫,將數(shù)據(jù)保存在內存中,,讀寫效率要比傳統(tǒng)的將數(shù)據(jù)保存在磁盤上的數(shù)據(jù)庫要快很多,。所以,監(jiān)控 Redis 的內存消耗并了解 Redis 內存模型對高效并長期穩(wěn)定使用 Redis 至關重要,。 內存使用統(tǒng)計通過 info memory 命令可以獲得 Redis 內存相關的指標,。較為重要的指標和解釋如下所示: 當 memfragmentationratio > 1 時,說明有部分內存并沒有用于數(shù)據(jù)存儲,而是被內存碎片所消耗,,如果該值很大,,說明碎片率嚴重。當 memfragmentationratio < 1 時,,這種情況一般出現(xiàn)在操作系統(tǒng)把 Redis 內存交換 (swap) 到硬盤導致,,出現(xiàn)這種情況要格外關注,由于硬盤速度遠遠慢于內存,,Redis 性能會變得很差,,甚至僵死。 當 Redis 內存超出可以獲得內存時,,操作系統(tǒng)會進行 swap,,將舊的頁寫入硬盤。從硬盤讀寫大概比從內存讀寫要慢5個數(shù)量級,。used_memory 指標可以幫助判斷 Redis 是否有被swap的風險或者它已經被swap,。 在 Redis Administration 一文 (鏈接在文末) 建議要設置和內存一樣大小的交換區(qū),如果沒有交換區(qū),,一旦 Redis 突然需要的內存大于當前操作系統(tǒng)可用內存時,,Redis 會因為 out of memory 而被 Linix Kernel 的 OOM Killer 直接殺死。雖然當 Redis 的數(shù)據(jù)被換出 (swap out) 時,,Redis的性能會變差,,但是總比直接被殺死的好。 Redis 使用 maxmemory 參數(shù)限制最大可用內存,。限制內存的目的主要有:
maxmemory 限制的是 Redis 實際使用的內存量,也就是 used_memory 統(tǒng)計項對應的內存,。實際消耗的內存可能會比 maxmemory 設置的大,,要小心因為這部內存導致 OOM。所以,,如果你有 10GB 的內存,,最好將 maxmemory 設置為 8 或者 9G 內存消耗劃分Redis 進程內消耗主要包括:自身內存 + 對象內存 + 緩沖內存 + 內存碎片,其中 Redis 空進程自身內存消耗非常少,,通常 usedmemoryrss 在 3MB 左右時,,used_memory 一般在 800KB 左右,一個空的 Redis 進程消耗內存可以忽略不計,。 對象內存對象內存是 Redis 內存占用最大的一塊,,存儲著用戶所有的數(shù)據(jù),。Redis 所有的數(shù)據(jù)都采用 key-value 數(shù)據(jù)類型,每次創(chuàng)建鍵值對時,,至少創(chuàng)建兩個類型對象:key 對象和 value 對象,。對象內存消耗可以簡單理解為這兩個對象的內存消耗之和(還有類似過期之類的信息)。鍵對象都是字符串,,在使用 Redis 時很容易忽略鍵對內存消耗的影響,,應當避免使用過長的鍵。 緩沖內存緩沖內存主要包括:客戶端緩沖,、復制積壓緩沖區(qū)和 AOF 緩沖區(qū),。 客戶端緩沖指的是所有接入到 Redis 服務器 TCP 連接的輸入輸出緩沖。 輸入緩沖無法控制,,最大空間為 1G,,如果超過將斷開連接。而且輸入緩沖區(qū)不受 maxmemory 控制,,假設一個 Redis 實例設置了 maxmemory 為 4G,,已經存儲了 2G 數(shù)據(jù),但是如果此時輸入緩沖區(qū)使用了 3G,,就已經超出了 maxmemory 限制,,可能導致數(shù)據(jù)丟失、鍵值淘汰或者 OOM,。 輸入緩沖區(qū)過大主要是因為 Redis 的處理速度跟不上輸入緩沖區(qū)的輸入速度,,并且每次進入輸入緩沖區(qū)的命令包含了大量的 bigkey。 輸出緩沖通過參數(shù) client-output-buffer-limit 控制,,其格式如下所示,。
hard limit 是指一旦緩沖區(qū)大小達到了這個閾值,Redis 就會立刻關閉該連接,。而 soft limit 和時間 duration 共同生效,,比如說 soft time 為 64mb、duration 為 60,,則只有當緩沖區(qū)持續(xù) 60s 大于 64mb 時,,Redis 才會關閉該連接。 普通客戶端是除了復制和訂閱的客戶端之外的所有連接,。Reids 對其的默認配置是 client-output-buffer-limit normal 0 0 0 , Redis 并沒有對普通客戶端的輸出緩沖區(qū)做限制,,一般普通客戶端的內存消耗可以忽略不計,,但是當有大量慢連接客戶端接入時這部分內存消耗就不能忽略,,可以設置 maxclients 做限制。特別當使用大量數(shù)據(jù)輸出的命令且數(shù)據(jù)無法及時推送到客戶端時,,如 monitor 命令,,容易造成 Redis 服務器內存突然飆升,。相關案例可以查看這篇文章美團在Redis上踩過的一些坑-3.redis內存占用飆升。 從客戶端用于主從復制,,主節(jié)點會為每個從節(jié)點單獨建立一條連接用于命令復制,,默認配置為 client-output-buffer-limit slave 256mb 64mb 60。當主從節(jié)點之間網絡延遲較高或主節(jié)點掛載大量從節(jié)點時這部分內存消耗將占用很大一部分,,建議主節(jié)點掛載的從節(jié)點不要多于 2 個,,主從節(jié)點不要部署在較差的網絡環(huán)境下,如異地跨機房環(huán)境,,防止復制客戶端連接緩慢造成溢出,。與主從復制相關的一共有兩類緩沖區(qū),一個是從客戶端輸出緩沖區(qū),,另外一個是下面會介紹到的復制積壓緩沖區(qū),。 訂閱客戶端用于發(fā)布訂閱功能,連接客戶端使用單獨的輸出緩沖區(qū),,默認配置為 client-output-buffer-limit pubsub 32mb 8mb 60,,當訂閱服務的消息生產快于消費速度時,輸出緩沖區(qū)會產生積壓造成內存空間溢出,。 輸入輸出緩沖區(qū)在大流量場景中容易失控,,造成 Redis 內存不穩(wěn)定,需要重點監(jiān)控,??梢远ㄆ趫?zhí)行 client list 命令,監(jiān)控每個客戶端的輸入輸出緩沖區(qū)大小和其他信息,。
client list 命令執(zhí)行速度慢,,客戶端較多時頻繁執(zhí)行存在阻塞redis的可能,所以一般可以先使用 info clients 命令獲取最大的客戶端緩沖區(qū)大小,。
復制積壓緩沖區(qū)是Redis 在 2.8 版本后提供的一個可重用的固定大小緩沖區(qū),,用于實現(xiàn)部分復制功能。根據(jù) repl-backlog-size 參數(shù)控制,,默認 1MB,。對于復制積壓緩沖區(qū)整個主節(jié)點只有一個,所有的從節(jié)點共享此緩沖區(qū),。因此可以設置較大的緩沖區(qū)空間,,比如說 100MB,可以有效避免全量復制,。 AOF 重寫緩沖區(qū):這部分空間用于在 Redis AOF 重寫期間保存最近的寫入命令,。AOF 重寫緩沖區(qū)的大小用戶無法控制,取決于 AOF 重寫時間和寫入命令量,,不過一般都很小,。 Redis 內存碎片Redis 默認的內存分配器采用 jemalloc,,可選的分配器還有:glibc、tcmalloc,。內存分配器為了更好地管理和重復利用內存,,分配內存策略一般采用固定范圍的內存塊進行分配。具體的分配策略后續(xù)會具體講解,,但是 Redis 正常碎片率一般在 1.03 左右(為什么是這個值),。但是當存儲的數(shù)據(jù)長度長度差異較大時,以下場景容易出現(xiàn)高內存碎片問題:
這部分內容我們后續(xù)再詳細講解 jemalloc,,因為大量的框架都會使用內存分配器,比如說 Netty 等,。 子進程內存消耗子進程內存消耗主要指執(zhí)行 AOF 重寫 或者進行 RDB 保存時 Redis 創(chuàng)建的子進程內存消耗,。Redis 執(zhí)行 fork 操作產生的子進程內存占用量表現(xiàn)為與父進程相同,理論上需要一倍的物理內存來完成相應的操作,。但是 Linux 具有寫時復制技術 (copy-on-write),,父子進程會共享相同的物理內存頁,當父進程處理寫請求時會對需要修改的頁復制出一份副本完成寫操作,,而子進程依然讀取 fork 時整個父進程的內存快照,。 如上圖所示,fork 時只拷貝 page table,,也就是頁表,。只有等到某一頁發(fā)生修改時,才真正進行頁的復制,。 但是 Linux Kernel 在 2.6.38 內存增加了 Transparent Huge Pages (THP) 機制,,簡單理解,它就是讓頁大小變大,,本來一頁為 4KB,,開啟 THP 機制后,一頁大小為 2MB,。它雖然可以加快 fork 速度( 要拷貝的頁的數(shù)量減少 ),,但是會導致 copy-on-write 復制內存頁的單位從 4KB 增大為 2MB,如果父進程有大量寫命令,,會加重內存拷貝量,,都是修改一個頁的內容,,但是頁單位變大了,,從而造成過度內存消耗,。例如,以下兩個執(zhí)行 AOF 重寫時的內存消耗日志:
這兩個日志出自同一個 Redis 進程,,used_memory 總量是 1.5GB,,子進程執(zhí)行期間每秒寫命令量都在 200 左右。當分別開啟和關閉 THP 時,,子進程內存消耗有天壤之別,。所以,在高并發(fā)寫的場景下開啟 THP,,子進程內存消耗可能是父進程的數(shù)倍,,造成機器物理內存溢出。 所以說,,Redis 產生的子進程并不需要消耗 1 倍的父進程內存,,實際消耗根據(jù)期間寫入命令量決定,所以需要預留一些內存防止溢出,。并且建議關閉系統(tǒng)的 THP,,防止 copy-on-write 期間內存過度消耗。不僅是 Redis,,部署 MySQL 的機器一般也會關閉 THP,。 |
|
來自: 西北望msm66g9f > 《編程》