偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

換一種存儲方式,居然能節(jié)約這么多內(nèi)存?

開發(fā) 前端
提到緩存,就會想到redis,提到 Redis,我們的腦子里馬上就會出現(xiàn)一個詞:快。首先我們從Redis的數(shù)據(jù)類型開始看起。

前言

提到緩存,就會想到redis,提到 Redis,我們的腦子里馬上就會出現(xiàn)一個詞:快。那么我們也知道,redis 之所以這么快,因為數(shù)據(jù)是放在內(nèi)存中的,但是內(nèi)存是非常昂貴的,怎么來設計我們的應用的存儲結(jié)構(gòu),讓應用滿足正常的業(yè)務的前提下來節(jié)約內(nèi)存呢?首先我們從Redis的數(shù)據(jù)類型開始看起。

Redis 的數(shù)據(jù)類型及底層實現(xiàn)

說到redis的數(shù)據(jù)類型,大家肯定會說:不就是 String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)嗎?”

其實,這些只是 Redis 鍵值對中值的數(shù)據(jù)類型,也就是數(shù)據(jù)的保存形式。

而這里,我們說的數(shù)據(jù)結(jié)構(gòu),是要去看看它們的底層實現(xiàn)。簡單說底層數(shù)據(jù)結(jié)構(gòu)一共有 6 種:

  • 簡單動態(tài)字符串
  • 雙向鏈表
  • 壓縮列表
  • 哈希表
  • 跳表和整數(shù)數(shù)組。

Redis 存儲結(jié)構(gòu)總覽

其實在Redis中,并不是單純將key 與value保存到內(nèi)存中就可以的。它需要依賴上面講的數(shù)據(jù)結(jié)構(gòu)對其進行管理。

換一種存儲方式,居然能節(jié)約這么多內(nèi)存?

因為這個哈希表保存了所有的鍵值對,所以,我也把它稱為全局哈希表。哈希表的最大好處很明顯,就是讓我們可以用 O(1) 的時間復雜度來快速查找到鍵值對——我們只需要計算鍵的哈希值,就可以知道它所對應的哈希桶位置,然后就可以訪問相應的 entry 元素。每個entry 都會有一個數(shù)據(jù)類型。

Redis 不同數(shù)據(jù)類型的編碼

而且每個類型又對應了多種編碼格式,不同的編碼格式對應的底層數(shù)據(jù)結(jié)構(gòu)是不同的。可以先做個了解,后面會具體用到。

編碼編碼對應的底層數(shù)據(jù)結(jié)構(gòu)INTlong 類型的整數(shù)EMBSTRembstr 編碼的簡單動態(tài)字符串RAW簡單動態(tài)字符串HT字典 HASH_TABLELINKEDLIST雙端鏈表ZIPLIST壓縮列表INTSET整數(shù)集合SKIPLIST跳躍表和字典

類型與編碼映射

類型編碼編碼對應的底層數(shù)據(jù)結(jié)構(gòu)STRINGINTlong 類型的整數(shù)STRINGEMBSTRembstr 編碼的簡單動態(tài)字符串STRINGRAW簡單動態(tài)字符串LISTZIPLIST壓縮列表LISTQUICKLIST快速列表LISTLINKEDLIST雙端鏈表HASHZIPLIST壓縮列表HASHHT字典SETINTSET整數(shù)集合SETHT字典ZSETZIPLIST壓縮列表ZSETSKIPLIST跳躍表和字典

具體的映射關(guān)系

1. 字符串類型(STRING)對象

2. 集合類型(SET)對象

3. 有序集合類型(ZSET)對象

有序集合類型的對象有兩種編碼方式:OBJ_ENCODING_SKIPLIST、OBJ_ENCODING_ZIPLIST。Redis 對于有序集合類型的編碼有兩個配置項:

  • zset_max_ziplist_entries,默認值為 128,表示有序集合中壓縮列表節(jié)點的最大數(shù)量。
  • zset_max_ziplist_value,默認值為 64,表示有序集合中壓縮列表節(jié)點的最大長度。

注:當刪除或更新元素,使得滿足以上兩個配置項時,編碼方式是不會自動從 OBJ_ENCODING_SKIPLIST 轉(zhuǎn)化為 OBJ_ENCODING_ZIPLIST 的,但 Redis 提供了函數(shù)

zsetConvertToZiplistIfNeeded 支持。

4. 哈希類型(HASH)對象

哈希類型對象有兩種編碼方式:OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_HT。Redis 對于哈希類型對象的編碼有兩個配置項:

  • hash_max_ziplist_entries,默認值 512, 表示哈希類型對象中壓縮列表節(jié)點的最大數(shù)量。
  • hash_max_ziplist_value,默認值 64,表示哈希類型對象中壓縮列表節(jié)點的最大長度。

注:當刪除或更新元素,使得滿足以上兩個配置項時,編碼方式是不會自動從 OBJ_ENCODING_HT 向 OBJ_ENCODING_ZIPLIST 轉(zhuǎn)化。

關(guān)于壓縮鏈表

因為這個和我們后面的優(yōu)化有關(guān)系,我們先來看看什么是壓縮鏈表。

壓縮列表實際上類似于一個數(shù)組,數(shù)組中的每一個元素都對應保存一個數(shù)據(jù)。和數(shù)組不同的是,壓縮列表在表頭有三個字段 zlbytes、zltail 和 zllen,分別表示列表長度、列表尾的偏移量和列表中的 entry 個數(shù);壓縮列表在表尾還有一個 zlend,表示列表結(jié)束。

換一種存儲方式,居然能節(jié)約這么多內(nèi)存?

prev_len,表示前一個 entry 的長度。prev_len 有兩種取值情況:1 字節(jié)或 5 字節(jié)。取值 1 字節(jié)時,表示上一個 entry 的長度小于 254 字節(jié)。雖然 1 字節(jié)的值能表示的數(shù)值范圍是 0 到 255,但是壓縮列表中 zlend 的取值默認是 255,因此,就默認用 255 表示整個壓縮列表的結(jié)束,其他表示長度的地方就不能再用 255 這個值了。所以,當上一個 entry 長度小于 254 字節(jié)時,prev_len 取值為 1 字節(jié),否則,就取值為 5 字節(jié)。

  • len:表示自身長度,4 字節(jié);
  • encoding:表示編碼方式,1 字節(jié);
  • content:保存實際數(shù)據(jù)。

壓縮鏈表的查詢

壓縮列表的設計不是為了查詢的,而是為了減少內(nèi)存的使用和內(nèi)存的碎片化。比如一個列表中的只保存int,結(jié)構(gòu)上還需要兩個額外的指針prev和next,每添加一個結(jié)點都這樣。而壓縮列表是將這些數(shù)據(jù)集合起來只需要一個prev和next。

在壓縮列表中,如果我們要查找定位第一個元素和最后一個元素,可以通過表頭三個字段的長度直接定位,復雜度是 O(1)。而查找其他元素時,就沒有這么高效了,只能逐個查找,此時的復雜度就是 O(N) 了。

為什么 String 類型內(nèi)存開銷大?

對于kv 類型的緩存數(shù)據(jù),我們經(jīng)常會用redis string 類型。比如日常工作中經(jīng)常對合作方客戶一周內(nèi)是否營銷進行緩存,key 為 32位的hash(用戶編碼),value 為是否(0或者1)營銷。很符合string 的數(shù)據(jù)類型。

但是隨著營銷數(shù)據(jù)量的不斷增加,我們的 Redis 內(nèi)存使用量也在增加,結(jié)果就遇到了大內(nèi)存 Redis 實例因為生成 RDB 而響應變慢的問題。很顯然,String 類型并不是一種好的選擇,需要進一步尋找能節(jié)省內(nèi)存開銷的數(shù)據(jù)類型方案。

通過上面的文章,我們對string 類型進行研究,會發(fā)現(xiàn):

當你保存 64 位有符號整數(shù)時,String 類型會把它保存為一個 8 字節(jié)的 Long 類型整數(shù),這種保存方式通常也叫作 int 編碼方式。但是,當你保存的數(shù)據(jù)中包含字符時,String 類型就會用簡單動態(tài)字符串(Simple Dynamic String,SDS)結(jié)構(gòu)體來保存,

另一方面,當保存的是字符串數(shù)據(jù),并且字符串小于等于 44 字節(jié)時,RedisObject 中的元數(shù)據(jù)、指針和 SDS 是一塊連續(xù)的內(nèi)存區(qū)域,這樣就可以避免內(nèi)存碎片。這種布局方式也被稱為 embstr 編碼方式。

int、embstr 和 raw 這三種編碼模式,如下所示:

換一種存儲方式,居然能節(jié)約這么多內(nèi)存?

根據(jù)文章開頭的示意圖,redis 全局hash 表中的 entry 元素 其實是dictEntry,dictEntry 結(jié)構(gòu)中有三個 8 字節(jié)的指針,分別指向 key、value 以及下一個 dictEntry,三個指針共 24 字節(jié),如下圖所示:

換一種存儲方式,居然能節(jié)約這么多內(nèi)存?

而且redis 模式使用jemalloc進行內(nèi)存管理, jemalloc 在分配內(nèi)存時,會根據(jù)我們申請的字節(jié)數(shù) N,找一個比 N 大,但是最接近 N 的 2 的冪次數(shù)作為分配的空間,這樣可以減少頻繁分配的次數(shù)。所以,我們用string這種存儲簡單數(shù)據(jù)的方式比較浪費內(nèi)存!!

用什么數(shù)據(jù)結(jié)構(gòu)可以節(jié)省內(nèi)存?

那么,用什么數(shù)據(jù)結(jié)構(gòu)可以節(jié)約內(nèi)存呢?就是我們上面講的壓縮列表(ziplist),這是一種非常節(jié)省內(nèi)存的結(jié)構(gòu)。

通過前文編碼對應的底層數(shù)據(jù)結(jié)構(gòu)我們了解到,使用壓縮鏈表實現(xiàn)的數(shù)據(jù)結(jié)構(gòu)有 List、Hash 和 Sorted Set 這樣的集合類型。

基于壓縮列表最大好處就是節(jié)省了 dictEntry 的開銷。當你用 String 類型時,一個鍵值對就有一個 dictEntry。當采用集合類型時,一個 key 就對應一個集合的數(shù)據(jù),能保存的數(shù)據(jù)多了很多,但也只用了一個 dictEntry,這樣就節(jié)省了內(nèi)存。

通過研究hash數(shù)據(jù)結(jié)構(gòu)。我發(fā)現(xiàn),hash類型有非常節(jié)省內(nèi)存空間的底層實現(xiàn)結(jié)構(gòu),但是,hash類型保存的數(shù)據(jù)模式,是一個鍵對應一系列值,并不適合直接保存單值的鍵值對。所以就使用二級編碼【二級編碼:把32位前3位作為key,后29位作為field】的方法,實現(xiàn)了用集合類型保存單值鍵值對,Redis 實例的內(nèi)存空間消耗明顯下降了。

實驗數(shù)據(jù)

我們模擬20w數(shù)據(jù)的寫入,看看string 類型和hash 類型分別對內(nèi)存的占用情況。

string類型:

  1. Long id = 10000000000l; 
  2.     for (Long i=0l;i<200000l;i++){ 
  3.         id+=i; 
  4.         System.out.println(i); 
  5.         try { 
  6.             String encode = MD5.encode(id+""); 
  7.             jCacheClient.set(encode,"1"); 
  8.             sleeptime(1);//防止qps 過高 
  9.         } catch (Exception e) { 
  10.             e.printStackTrace(); 
  11.             sleeptime(1000); 
  12.         } 
  13.     } 
  1. flushdb 
  2. +OK 
  3. info memory 
  4. $1165 
  5. # Memory 
  6. used_memory:135261368 
  7.  
  8.  
  9. info memory 
  10. $1166 
  11. # Memory 
  12. used_memory:156517632 
  13. 使用 20.27m 

hash類型

  1. Long id = 10000000000l; 
  2.     for (Long i=0l;i<200000l;i++){ 
  3.         id+=i; 
  4.         try { 
  5.             String encode = MD5.encode(id+""); 
  6.             String prex = encode.substring(0,3); 
  7.             String key = encode.substring(3,32); 
  8.             jCacheClient.hset(prex,key,"1"); 
  9.             sleeptime(1); 
  10.         } catch (Exception e) { 
  11.             e.printStackTrace(); 
  12.             sleeptime(1000); 
  13.         } 
  14.     } 
  1. flushdb 
  2. +OK 
  3. info memory 
  4. $1165 
  5. # Memory 
  6. used_memory:135220400 
  7.  
  8.  
  9. info memory 
  10. $1166 
  11. # Memory 
  12. used_memory:142697280 
  13. 內(nèi)存使用 7.13M 

只是改了一個存儲結(jié)構(gòu),內(nèi)存節(jié)約了大概2/3.

二級編碼使用注意事項

因為Redis Hash 類型的兩種底層實現(xiàn)結(jié)構(gòu),分別是壓縮列表和哈希表。

那么,Hash 類型底層結(jié)構(gòu)什么時候使用壓縮列表,什么時候使用哈希表呢?根據(jù)上面表格的內(nèi)容,我們知道有兩個閾值:

  • hash-max-ziplist-entries:表示用壓縮列表保存時哈希集合中的最大元素個數(shù)。
  • hash-max-ziplist-value:表示用壓縮列表保存時哈希集合中單個元素的最大長度。

如果我們往 Hash 集合中寫入的元素個數(shù)超過了 hash-max-ziplist-entries,或者寫入的單個元素大小超過了 hash-max-ziplist-value,Redis 就會自動把 Hash 類型的實現(xiàn)結(jié)構(gòu)由壓縮列表轉(zhuǎn)為哈希表。一旦從壓縮列表轉(zhuǎn)為了哈希表,Hash 類型就會一直用哈希表進行保存,而不會再轉(zhuǎn)回壓縮列表了。在節(jié)省內(nèi)存空間方面,哈希表就沒有壓縮列表那么高效了。

所以要根據(jù)實際情況調(diào)整二級編碼的實現(xiàn)方式,節(jié)約內(nèi)存,提高redis的響應速度!

通過 config get 命令 我們可以查看 閥值的設置:

換一種存儲方式,居然能節(jié)約這么多內(nèi)存?

 

責任編輯:姜華 來源: 今日頭條
相關(guān)推薦

2024-04-02 08:41:10

ArrayListSubList場景

2019-10-28 11:30:43

架構(gòu)數(shù)據(jù)結(jié)構(gòu)布隆過濾器

2021-09-28 12:25:30

數(shù)據(jù)庫

2019-07-22 15:59:21

2009-02-26 10:29:00

2014-03-07 10:46:49

編程語言趣味

2023-01-26 23:46:15

2023-07-07 19:23:08

微軟文字Claude

2020-11-20 10:22:34

代碼規(guī)范設計

2020-12-01 08:19:15

Redis

2013-08-12 09:31:39

Windows操作系統(tǒng)

2017-08-11 14:21:33

軟件開發(fā)前端框架

2023-07-17 08:21:52

漏洞版本項目

2021-06-05 07:33:09

ID分布式架構(gòu)

2023-11-13 08:49:54

2018-07-18 08:59:32

Redis存儲模式

2018-06-26 15:00:24

Docker安全風險

2024-07-12 09:35:38

前端工具檢驗

2024-02-20 08:09:51

Java 8DateUtilsDate工具類

2023-09-11 11:53:51

物聯(lián)網(wǎng)協(xié)議物聯(lián)網(wǎng)
點贊
收藏

51CTO技術(shù)棧公眾號