自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

前言

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

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

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

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

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

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

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

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

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

因?yàn)檫@個(gè)哈希表保存了所有的鍵值對(duì),所以,我也把它稱(chēng)為全局哈希表。哈希表的最大好處很明顯,就是讓我們可以用 O(1) 的時(shí)間復(fù)雜度來(lái)快速查找到鍵值對(duì)——我們只需要計(jì)算鍵的哈希值,就可以知道它所對(duì)應(yīng)的哈希桶位置,然后就可以訪(fǎng)問(wèn)相應(yīng)的 entry 元素。每個(gè)entry 都會(huì)有一個(gè)數(shù)據(jù)類(lèi)型。

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

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

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

類(lèi)型與編碼映射

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

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

1. 字符串類(lèi)型(STRING)對(duì)象

2. 集合類(lèi)型(SET)對(duì)象

3. 有序集合類(lèi)型(ZSET)對(duì)象

有序集合類(lèi)型的對(duì)象有兩種編碼方式:OBJ_ENCODING_SKIPLIST、OBJ_ENCODING_ZIPLIST。Redis 對(duì)于有序集合類(lèi)型的編碼有兩個(gè)配置項(xiàng):

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

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

zsetConvertToZiplistIfNeeded 支持。

4. 哈希類(lèi)型(HASH)對(duì)象

哈希類(lèi)型對(duì)象有兩種編碼方式:OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_HT。Redis 對(duì)于哈希類(lèi)型對(duì)象的編碼有兩個(gè)配置項(xiàng):

  • hash_max_ziplist_entries,默認(rèn)值 512, 表示哈希類(lèi)型對(duì)象中壓縮列表節(jié)點(diǎn)的最大數(shù)量。
  • hash_max_ziplist_value,默認(rèn)值 64,表示哈希類(lèi)型對(duì)象中壓縮列表節(jié)點(diǎn)的最大長(zhǎng)度。

注:當(dāng)刪除或更新元素,使得滿(mǎn)足以上兩個(gè)配置項(xiàng)時(shí),編碼方式是不會(huì)自動(dòng)從 OBJ_ENCODING_HT 向 OBJ_ENCODING_ZIPLIST 轉(zhuǎn)化。

關(guān)于壓縮鏈表

因?yàn)檫@個(gè)和我們后面的優(yōu)化有關(guān)系,我們先來(lái)看看什么是壓縮鏈表。

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

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

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

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

壓縮鏈表的查詢(xún)

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

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

為什么 String 類(lèi)型內(nèi)存開(kāi)銷(xiāo)大?

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

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

通過(guò)上面的文章,我們對(duì)string 類(lèi)型進(jìn)行研究,會(huì)發(fā)現(xiàn):

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

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

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

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

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

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

而且redis 模式使用jemalloc進(jìn)行內(nèi)存管理, jemalloc 在分配內(nèi)存時(shí),會(huì)根據(jù)我們申請(qǐng)的字節(jié)數(shù) N,找一個(gè)比 N 大,但是最接近 N 的 2 的冪次數(shù)作為分配的空間,這樣可以減少頻繁分配的次數(shù)。所以,我們用string這種存儲(chǔ)簡(jiǎn)單數(shù)據(jù)的方式比較浪費(fèi)內(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)。

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

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

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

實(shí)驗(yàn)數(shù)據(jù)

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

string類(lèi)型:

  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 過(guò)高 
  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類(lèi)型

  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 

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

二級(jí)編碼使用注意事項(xiàng)

因?yàn)镽edis Hash 類(lèi)型的兩種底層實(shí)現(xiàn)結(jié)構(gòu),分別是壓縮列表和哈希表。

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

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

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

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

通過(guò) config get 命令 我們可以查看 閥值的設(shè)置:

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

 

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

2024-04-02 08:41:10

ArrayListSubList場(chǎng)景

2019-10-28 11:30:43

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

2021-09-28 12:25:30

數(shù)據(jù)庫(kù)

2019-07-22 15:59:21

2009-02-26 10:29:00

2023-01-26 23:46:15

2023-07-07 19:23:08

微軟文字Claude

2014-03-07 10:46:49

編程語(yǔ)言趣味

2020-12-01 08:19:15

Redis

2017-08-11 14:21:33

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

2023-07-17 08:21:52

漏洞版本項(xiàng)目

2013-08-12 09:31:39

Windows操作系統(tǒng)

2020-11-20 10:22:34

代碼規(guī)范設(shè)計(jì)

2018-07-18 08:59:32

Redis存儲(chǔ)模式

2021-06-05 07:33:09

ID分布式架構(gòu)

2018-06-26 15:00:24

Docker安全風(fēng)險(xiǎn)

2024-07-12 09:35:38

前端工具檢驗(yàn)

2023-11-13 08:49:54

2024-02-20 08:09:51

Java 8DateUtilsDate工具類(lèi)

2023-09-11 11:53:51

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

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