Redis學習-內存優化

如下爲我的學習Redis的備忘錄--內存優化,基於Redis4.0.2redis

1.隨時查看info memory,瞭解內存使用情況
127.0.0.1:6379> info memory
# Memory
used_memory:2314624 //(字節單位形式)
used_memory_human:2.21M  //Redis已分配的內存總量(易讀單位形式)
used_memory_rss:1282048
used_memory_rss_human:1.22M //操做系統爲Redis進程分配的內存總量
used_memory_peak:18010560
used_memory_peak_human:17.18M //最大使用內存總量(峯值)
used_memory_peak_perc:12.85% 
used_memory_overhead:2078792
used_memory_startup:963088
used_memory_dataset:235832
used_memory_dataset_perc:17.45%
total_system_memory:4294967296
total_system_memory_human:4.00G
used_memory_lua:37888
used_memory_lua_human:37.00K //緩存Lua腳本佔用的內存
maxmemory:0
maxmemory_human:0B //最大內存限制,0表示無限制
maxmemory_policy:noeviction //超過內存限制後的處理策略
mem_fragmentation_ratio:0.55 //碎片率(used_memory_rss/used_memory的比值),>1表示有碎片,<1表示部分Redis的內存被系統交換到硬盤(此時Redis性能變差)
mem_allocator:libc 
active_defrag_running:0
lazyfree_pending_objects:0

2.Redis主進程的內存消耗:
  • Redis自身使用的內存:消耗不多,3MB多點
  • 對象內存
  • 緩衝內存
  • 內存碎片
2.1對象內存:全部key對象長度 + 全部value對象長度
  • 每次建立鍵值對時,至少建立兩個類型對象:key對象、value對象,應該使用短鍵名
2.2緩衝內存
  • 每一個客戶端的輸入、輸出緩衝內存:
    • 輸入緩衝最大1G,超出則關閉該客戶端鏈接;
    • 輸出緩衝:16KB的固定緩衝區、動態緩衝區,動態緩衝區可經過client-output-buffer-limit配置參數限制(根據客戶端類型normal、slave、pubsub,分開設置)
      • client-output-buffer-limit normal 0 0 0
      • client-output-buffer-limit slave 256mb 64mb 60 //超過256MB時,或者持續超過64MB達60秒,關閉鏈接
      • client-output-buffer-limit pubsub 32mb 8mb 60
  • 複製積壓緩衝內存:用於主從複製的部分複製,全部客戶端共享該緩衝區,默認1MB,可經過repl-backlog-size調整,適當調大,可有效避免全量複製;
  • AOF緩衝內存:用於保存在AOF重寫期間的寫命令,便於重寫完畢後把緩衝的命令追加到AOF文件中;
2.3內存碎片
  • 當存儲的數據長短差別較大時,就容易出現大量內存碎片,應該儘量地保持數據對齊或使用固定長度的字符串;
  • 內存碎片只能經過徹底重啓Redis來清除;
3.Redis子進程內存消耗
  • 在執行AOF重寫和RDB快照持久化時,會fork一個子進程,父子進程將共享此刻的內存快照,期間,在Linux下使用寫時複製技術:父進程會爲新進的寫命令請求須要修改的內存頁複製出一份副原本完成寫操做,子進程結束後,父進程再把該副本覆蓋回原來的內存頁。
  • Linux默認開啓的THP把寫時複製期間的內存頁複製單位從4KB變爲2MB,加大了持久化時的內存消耗,應該關閉該功能:sudo echo never > /sys/kernel/mm/transparent_hugepage/enabled
4.內存管理
  • 設置內存上限,並指定內存回收策略;
  • maxmemory配置參數可限制當前Redis實例可以使用的最大內存;
  • 經過config set maxmemory可根據業務需求,動態調整內存限制;
  • 經過設置內存上限,可方便地在一臺服務器上部署多個Redis實例
4.1內存回收策略:
  • 爲鍵設置過時屬性,Redis採用惰性刪除和定時任務刪除機制實現過時鍵的內存回收;
    • 惰性刪除:在讀取鍵時才檢查是否過時
    • 定時任務刪除:經過hz配置參數設置頻率,默認每秒10次;
  • 內存溢出控制策略:共6中策略,經過maxmemory-policy配置參數控制,默認noeviction(不刪除,拒絕寫入,返回錯誤)
    • LRU算法表示最近最少使用的,LFU算法表示最不經常使用的:
      • #volatile-lru - >在設置了過時的key中,刪除最近最少使用的key,直到空間足夠爲止
      • #allkeys-lru - >從全部key裏刪除最近最少使用的key,無論有沒設置過時,直到空間足夠爲止
      • #volatile-lfu - >在設置了過時的key中,刪除最少使用的key,直到空間足夠爲止
      • #allkeys-lfu - >從全部key裏刪除最少使用的key,無論有沒設置過時,直到空間足夠爲止
      • #volatile-random - >刪除一個過時集合中的隨機key。
      • #allkeys-random - >刪除一個隨機key,無論有沒設置過時。
      • #volatile-ttl - >刪除即將過時的key(次TTL)
      • #noviction - >不刪除,拒絕寫入,寫入操做時返回錯誤。
  • maxmemory-samples 5 是說每次進行淘汰的時候,會隨機抽取5個key 從裏面淘汰最少使用的(默認選項)
  • 應避免內存溢出,由於在內存溢出且非noeviction策略時,會頻繁觸發回收內存的操做,影響Redis性能,如有從節點,還會把刪除命令同步給從節點;
  • 對於只作緩存的場景下,可經過調小maxmemory,並執行一次命令,若是使用非noeviction策略,則會一次性回收到maxmemory指定的內存使用量,實現內存的快速回收,但會致使數據丟失和短暫阻塞;
5.內存優化:
  • Redis存儲的全部數據都使用redisObject來封裝,包括string、hash、list、set、zset
  • redisObject的字段:
    • type字段:保存對象使用的數據類型,命令type {key}返回值對象的數據類型
    • encoding字段:保存對象使用的內部編碼類型,命令object encoding {key}返回值對象的內部編碼類型
    • lru字段:記錄對象最後一次被訪問的時間(用於內存回收),命令object idletime {key}查看鍵的空閒時間(可配合scan命令批量查找長期空閒的鍵進行清理)
    • refcount字段:記錄對象的引用計數(用於回收),命令object refcount {key}查看鍵的引用數
    • *ptr字段:存儲值對象的數據或指針,若是是整數,則直接存儲數據,不然表示指向數據的指針
  • 字符串長度在39字節之內對象,在建立redisObject封裝對象時只需分配內存1次,可提升性能;
  • 縮減鍵、值對象的長度:簡化鍵名,使用高效的序列化工具來序列化值對象,還可以使用壓縮工具(Google Snappy)壓縮序列化後的數據;
  • 共享對象池:Redis內部維護[0-9999]的整數對象池,對於0-9999的內部整數類型的元素、整數值對象都會直接引用整數對象池中的對象,所以儘可能使用整數對象可節省內存;
    • 注意:
      • 啓用LRU相關的溢出策略時,沒法使用共享對象池;
      • 對於ziplist編碼的值對象,也沒法使用共享對象池(成本太高);
  • Redis對字符串的優化
    • Redis全部key都是string類型,且value對象的數據除了整數以外,最終也都使用string來存儲;
    • Redis字符串結構採用SDS(內部簡單動態字符串):
      • int len字段:已用字節長度
      • int free字段:未用字節長度
      • char buf[]字段:字節數組
    • SDS字符串特色:
      • 獲取字符串長度、未用長度速度快,時間複雜度爲O(1)
      • 用字節數組保存數據,支持安全的二進制數據存儲
      • 內部實現了預分配內存機制,下降內存分配次數
      • 惰性刪除機制,字符串縮減後的空間不釋放,做爲預分配空間保留
    • SDS字符串內存預分配機制:
      • 首次建立時,不作預分配,數據恰好填滿字節數組,len字段爲字節數組長度,free字段爲0
      • 在修改字符後,若是本來的free空間不足,且當前總數據大小<1MB,則每次預分配1倍容量,而若是總數據大小>1MB,則每次預分配1MB容量。
      • 如:(忽略len、free字段所佔內存,只考慮buf所佔內存)
        • 對於首次建立的30字節字符串,對它執行append追加10字節,將使用(30+10)+40+1=81字節的內存
        • 而直接set這40字節的字符串,只使用41字節的內存(1字節爲結尾標識'\0')
    • 應該儘可能避免頻繁執行增加字符串的命令,如append、setrange,改成直接用set一次性建立字符串,減小預分配帶來的內存浪費和下降內存碎片率;
    • 字符串重構:編碼爲ziplist的hash數據結構的妙用1
      • 對於非簡單字符串數據,可用hash數據結構代替
      • 由於小hash使用ziplist編碼,可節省內存(字符串數據必須小於hash-max-ziplit-value配置的值)
      • 且hash可用使用hmget、hmset命令,支持field-value的部分讀取修改,而沒必要每次都總體存取 
6.合理設置內部編碼配置參數:
Redis的每種數據結構都有至少兩種內部數據編碼類型: object encoding {key}  獲取key對應的value對象的編碼類型
string int 8個字節的長整型
embstr <=39字節的字符串
raw >39字節的字符串(最大不能超過512MB)
hash ziplist 壓縮列表(模擬雙向鏈表),內存佔用少,但讀寫時間複雜度爲O(n²)
hashtable 哈希表,內存佔用較大,但讀寫時間複雜度爲O(1)
list quicklist (ziplist) 快速雙向鏈表(每一個節點都是ziplist)
set intset 整數集合
hashtable 哈希表
zset ziplist 壓縮列表
skiplist 跳躍表
  • Redis在寫入數據時自動完成編碼轉換,且在超過配置的限制值時將轉換爲新的內部編碼,動態修改限制參數不會回退爲舊編碼,只有在重啓Redis從新加載數據後纔會回退;
  • ziplist編碼:
    • ziplist內部結構:
      • zlbytes字段:int-32類型,記錄整個ziplist總字節數,便於從新調整ziplist空間;
      • zltail字段:記錄距離尾節點的偏移量,便於尾節點的彈出操做;
      • zllen字段:記錄節點數量;
      • entry1...entryN節點:記錄具體的節點,長度根據具體的數據
        • prev_entry_bytes_length:記錄前一節點所佔空間,用於快速定位前一節點實現列表的反向迭代;
        • encoding:當前節點編碼和長度,前兩位表示編碼類型(字符串、整數),其他位表示數據長度;
        • contents:保存節點的值,針對實際數據長度作內存佔用優化;
      • zlend字段:記錄列表結尾,佔1個字節
    • ziplist是一塊連續的內存,它模擬了雙向鏈表的功能,兩端的push和pop速度快,可是對中間元素的修改不方便,每次在中間插入、刪除都會引起內存從新分配和數據拷貝,ziplist越長性能越低,因此ziplist僅適合存儲小對象和長度有限的數據。
    • 所以,ziplist的長度不宜過長(建議1000個之內),且元素大小不宜過大(建議512字節之內),且最好每一個元素的大小差異不宜過大(不然碎片多)。
    • 對於較小的hash、zset 數據結構,Redis會自動使用ziplist編碼,雖然list的編碼爲quicklist,但list的節點也是ziplist編碼。
    • hash同時知足如下條件則使用ziplist編碼,超過則使用hashtable編碼
      • hash-max-ziplist-entries 64 
      • hash-max-ziplist-value 512 
    • list使用的是quicklist編碼,quicklist的每一個節點都是ziplist,如下指定節點的設置
      • list-max-ziplist-size -2 //>0時表示每一個節點最多包含幾個數據項,即ziplist的長度。<0時,只能取-5~-1,指每一個節點ziplist的最大字節大小≤64KB~4KB字節(超過該限制時,則新建一個節點)
      • list-compress-depth n //n表示兩端不被壓縮的節點個數(壓縮全部中間節點),默認0不壓縮
    • zset同時知足如下條件則使用ziplist編碼,超過則使用skiplist編碼
      • zset-max-ziplist-entries 128
      • zset-max-ziplist-value 64
    • set全部元素都爲整數,且個數小於如下參數時,使用intset編碼,不然使用hashtable編碼
      • set-max-intset-entries 512
  • intset編碼:
    • intset編碼是無序集合(set)類型編碼的一種,內部表現爲存儲有序、不重複的整數集合
    • intset結構:
      • encoding:根據集合內最長整數值肯定全部元素的類型(int-1六、int-3二、int-64),當插入一個更長的整數類型時,會觸發類型升級操做(會致使從新申請內存空間,並複製數據到新數組)
      • length:集合元素的個數;
      • contents:整數數組,按從小到大順序保存;
    • 因此,在使用set集合,且爲整數時,應該保持整數長度類型的一致性,避免內存浪費;
    • set小集合重構(編碼爲ziplist的hash數據結構的妙用2):由於當set集合中有一個是非整數時,將使用hashtable編碼,沒法使用intset實現內存優化,若是集合元素個數和大小知足hash的ziplist編碼條件,則此時可用hash類型來模擬集合,把hash的field設爲set的元素,而hash的value設爲1字節佔位符便可;
7.使用32位Redis實例:
  • 假如緩存數據小於4GB,就使用32位Redis實例,由於對於每個key,將使用更少的內存,指針佔用的字節數更少。
  • 使用make 32bit命令編譯生成32位的redis。但內存受限在4G內,不過他們的RDB和AOF文件是兼容在32位和64位的。
8.儘量的使用hash數據結構
  • 由於Redis在儲存小於100個字段的Hash結構上,其存儲效率是很是高的。因此在不須要集合(set)操做或list的push/pop操做的時候,儘量的使用hash結構
  • 使用單命令多參數的命令取代多命令單參數的命令:
    • set -> mset
    • get -> mget
    • lset -> lpush, rpush
    • lindex -> lrange
    • hset -> hmset
    • hget -> hmget 
9.減小key的數量(編碼爲ziplist的hash數據結構的妙用3)
  • 把大量value爲string的普通key-value抽象爲分組的小hash的field-value,建議field總個數<1000,value的長度<512字節,value越小,越省空間(最好50字節之內)
key = username0000 value =strs 
...
key = username9999 value =strs 
  • 以上可重構爲10組hash key,每組1000個field
key = username0 field = 000 value = str ... field =999 value =str 
...
key = username9 field = 000 value = str ... field =999 value =str 
  • 對於只含可計算的field的Hash:
    • 也可以使用分組hash:以下,每100個用戶ID共享一個hash key
      • key=userId/100, field1=userId%100, field1Value=str, field2=userId%100, field2Value=str, ...
      • 即:userId爲1~100的全部用戶的userId-value鍵值對都存儲在key=0的field-value中,而101~200則存在key=1中,......
相關文章
相關標籤/搜索