Redis學習日記(五) 記憶體管理

2021-09-08 17:12:46 字數 3417 閱讀 5935

這裡有3個內容我們得注意 : ①:used_memory :物理使用的記憶體空間(1.97m)。

② :user_memory_rss :作業系統認為redis使用的記憶體空間。這裡是(2.59m)。

③:mem_fragmentstion_ratio:② / ① 的值

> 1 表示記憶體碎片化嚴重;  < 1 說明redis記憶體存在硬碟化情況

①:redis 程序 本身所佔的記憶體,這部分記憶體不大,可以暫時不考慮。

②:物件記憶體 ,這部分是主要的記憶體。

③:快取記憶體,包括客戶端輸入輸出快取,aof快取(重寫的時候寫入命令),部分複製時的快取(預設1m)

上述的3種記憶體之和就是  used_memory,那就有問題了,為什麼會有 作業系統認為的記憶體(used_memory_rss)大於真正的記憶體呢?這裡就牽扯到了redis的記憶體分配器了,我們可以看到 info memory 的最後一項:men_allocator :jemalloc 。jemalloc是預設的記憶體分配器,它分配記憶體的時候按照 小、大、巨大三個範圍分配,舉例說 我們要儲存 5kb的內容,jemalloc分配的記憶體塊大小可能就是8kb,這時3kb的記憶體就是記憶體碎片,這部分不能繼續分配給其他物件。

知道了為什麼碎片的**,我們就知道了  used_memory + 碎片記憶體 =  used_memory_rss  。 mem_fragmentstion_ratio 的意義:這個值越大,說明記憶體空間碎片化越嚴重。

主要手段有兩種:控制記憶體上限 和 控制記憶體**策略。

控制記憶體上限很簡單,就是限制單點redis 的最大使用記憶體量,這裡的最大記憶體指的是 真正使用的記憶體量,也就是 used_memory,因為碎片記憶體的存在,所以實際消耗的記憶體量會大於 maxmemory。

設定 maxmemory 引數的值即可, 如動態的修改: config set maxmemory 4gb

什麼時候觸發記憶體**呢?

這裡有兩種情況:第一是在刪除過期key的時候;第二是記憶體達到 maxmemory 觸發記憶體**策略

刪除過期key的記憶體有兩種策略:

①:惰性刪除:過期的key不主動刪除,在客戶端使用這個key 的時候讀取key的過期時間,超時後刪除key並返回空值。這種策略節省了 cpu 的消耗,但是也容易造成記憶體洩漏,key長時間不用但是一直儲存在記憶體中,所以有了下面的這種刪除過期key的策略。

②:定時任務刪除:預設每秒執行10次定時任務,每次定時任務都會隨機檢查20個鍵,刪除過期的key。定時任務還啟用自適應演算法來根據鍵的過期時間比例和快慢兩種模式來**鍵。

maxmemory-policy 控制的記憶體**策略:

①:noeviction:預設策略,當達到記憶體上限的時候拒絕客戶端寫入,並報錯,但是讀命令還是能進行。

②:volatile-lru:根據lru(least recently used)刪除 過期 key,當過期key沒有時,回退到 noeviction 。

③:allkeys-lru :根據lru 刪除 key,直到騰出足夠空間。

④:volatile-random:隨機刪除 過期 key ,直到騰出足夠空間;

⑤:allkeys-random:隨機刪除 key,直到騰出足夠空間

⑥:volatile-ttl:根據鍵值物件的 ttl 屬性 ,刪除最近要過期的key,如果沒有,則回退到 noevicion。

頻繁進行記憶體的**成本非常大,主要包括查詢符合的可**鍵和刪除可**鍵。這裡如果有從節點,還要考慮主節點的刪除會使從節點也進行同步,放大了主節點的寫操作。

簡單的來說就是讓key 和value 的長度小一些,在乙個key能描述業務的情況下越短越好,如 user::name 可以變成 u::n 。 針對value 可以去掉不必要儲存的資料,其次可以將value 序列化儲存,採用高效的序列化方式,如protostuff ,kryo等。

redis內部維持了乙個 【0 ~ 9999】的整數物件池,當資料大量存在 【0 ~ 9999】 的資料的時候共享物件池能節省大量記憶體。但是這個共享物件池在 redis 使用 maxmemory + lru **策略的時候失效,為什麼呢?每隔 redis 物件都有乙個屬性叫做 lru,它記錄了這個物件最後的使用時間,物件共享意味著 多個 引用共享同乙個 物件,導致lru也被共享。如果沒有設定maxmemory,那麼就不會觸發 lru**,所以這時共享物件池也是可以使用的。共享物件池只有在 maxmemory + lru 模式下才會失效。

字串採用簡單動態字串(sds)的資料結構,內部存在預分配機制。

預分配機制通俗來說是個什麼回事呢? 在 對字串操作的時候,具體刪減字串長度的時候不釋放刪減的空間,而是當做預分配的空間保留,對字串追加的時候多分配一倍空間當做預分配空間(如 60位元組的字串追加60位元組,最後至少240位元組大小,這裡多出來的120位元組就是預分配空間),那這樣的預分配有啥好處呢?好處就是減少了記憶體分配的次數。

對於字串的預分配機制,我們應該小心它帶來的空間浪費。

字串重構,如json這樣的結構的資料改為使用如使用 hash結構來儲存,但是使用hash又會遇到一些問題需要注意,下面也會寫到。

上圖是redis 內部編碼的方式,這個編碼型別在資料寫入的時候即完成,隨著資料的變化編碼結構可能變化,但是編碼結構只能從小變大,並且不可逆。其實從編碼結構的變化也可以看做是由時間效率與空間效率的協調。

舉個例子,list 型別 最開始的編碼型別是ziplist,ziplist 的結構是帶有頭尾指標的連續記憶體空間,可以模擬為雙向鍊錶。訪問的時間複雜度為 o(n^2),在資料量小的時候採用ziplist編碼,這時因為資料量小即使時間複雜度為o(n^2)也有較高的時間效率,而ziplist的記憶體是非常小的,非常適合資料量小的時候使用,而當資料量大的時候,list編碼就會變成linkedlist來提高相應的時間效率,而犧牲空間效率(因為鍊錶的記憶體占用肯定比ziplist大)。

對於hash,list,zset 三種我們可以通過指定 -max-ziplist-value (value最大空間)和 -max-ziplist-entries (元素個數)來控制 編碼從 ziplist 到後一種編碼的轉換時機。

而對於set 的編碼優化,對於 整數型別的資料儲存時 set使用intset的編碼儲存,它的記憶體和寫入效率都非常高。當set型別的長度大於 set-max-intset-entries 或者不是整數型別的時候使用hashtable編碼。所以用set來儲存整數型別非常合適,可以把 set-max-intset-entries 設定大一些。

使用ziplist 的hash型別來儲存同等數量的 string型別的資料。

Linux記憶體管理系列之五 記憶體問題實戰

前面幾部分主要從原理上講解了linux核心如何管理核心態記憶體與使用者態記憶體,這部分我們重點 記憶體管理中一些實際工程上的效能問題。1 為何需要記憶體對齊?記憶體對齊是一種提供記憶體訪問速度的策略,cpu在訪問未對齊的記憶體需要經過兩次記憶體訪問,而經過記憶體對齊一次就可以。這是根據cpu位數決定...

C語言學習筆記(五) 記憶體分配

void getmemory char p,int num void test1 輸出內容 xmgcc void test11 char getmemory2 void test2 函式返回位址都是不安全的,因為函式結束後,函式變數的記憶體都會變釋放,因此這個位址其他運用程式也可以用到,會被修改。解...

OC重新開始(五)記憶體管理MRC

在程式裡,若執行過程中不但不能釋放不在使用的記憶體反而會不停的分配記憶體這樣占用的記憶體會越來越多,程式速度會越來越慢最後甚至會崩潰。在指標所指向的物件已經被釋放或 的情況下,改指標被稱為野指標或懸垂指標,繼續使用這樣的指標會造成程式崩潰。oc中通過向類物件傳送alloc訊息來生成例項物件,allo...