Redis內(nèi)存優(yōu)化

參考來源

Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常寶貴的資源。對于如何優(yōu)化內(nèi)存使用一直是Redis用戶非常關(guān)注的問題。本文讓我們深入到Redis細(xì)節(jié)中,學(xué)習(xí)內(nèi)存優(yōu)化的技巧。分為如下幾個(gè)部分:

一. redisObject對象
二. 縮減鍵值對象
三. 共享對象池
四. 字符串優(yōu)化
五. 編碼優(yōu)化
六. 控制key的數(shù)量

一. redisObject對象

Redis存儲(chǔ)的所有值對象在內(nèi)部定義為redisObject結(jié)構(gòu)體,內(nèi)部結(jié)構(gòu)如下圖所示。

Redis存儲(chǔ)的數(shù)據(jù)都使用redisObject來封裝,包括string,hash,list,set,zset在內(nèi)的所有數(shù)據(jù)類型。理解redisObject對內(nèi)存優(yōu)化非常有幫助,下面針對每個(gè)字段做詳細(xì)說明:

1.type字段:

表示當(dāng)前對象使用的數(shù)據(jù)類型,Redis主要支持5種數(shù)據(jù)類型:string,hash,list,set,zset。可以使用type {key}命令查看對象所屬類型,type命令返回的是值對象類型,鍵都是string類型。

2.encoding字段:

表示Redis內(nèi)部編碼類型,encoding在Redis內(nèi)部使用,代表當(dāng)前對象內(nèi)部采用哪種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。理解Redis內(nèi)部編碼方式對于優(yōu)化內(nèi)存非常重要 ,同一個(gè)對象采用不同的編碼實(shí)現(xiàn)內(nèi)存占用存在明顯差異,具體細(xì)節(jié)見之后編碼優(yōu)化部分。

3.lru字段:

記錄對象最后一次被訪問的時(shí)間,當(dāng)配置了 maxmemory和maxmemory-policy=volatile-lru | allkeys-lru 時(shí), 用于輔助LRU算法刪除鍵數(shù)據(jù)。可以使用object idletime {key}命令在不更新lru字段情況下查看當(dāng)前鍵的空閑時(shí)間。

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

開發(fā)提示:可以使用scan + object idletime 命令批量查詢哪些鍵長時(shí)間未被訪問,找出長時(shí)間不訪問的鍵進(jìn)行清理降低內(nèi)存占用。

</pre>

|

</figure>

4.refcount字段:

記錄當(dāng)前對象被引用的次數(shù),用于通過引用次數(shù)回收內(nèi)存,當(dāng)refcount=0時(shí),可以安全回收當(dāng)前對象空間。使用object refcount {key}獲取當(dāng)前對象引用。當(dāng)對象為整數(shù)且范圍在[0-9999]時(shí),Redis可以使用共享對象的方式來節(jié)省內(nèi)存。具體細(xì)節(jié)見之后共享對象池部分。

5. *ptr字段:

與對象的數(shù)據(jù)內(nèi)容相關(guān),如果是整數(shù)直接存儲(chǔ)數(shù)據(jù),否則表示指向數(shù)據(jù)的指針。Redis在3.0之后對值對象是字符串且長度<=39字節(jié)的數(shù)據(jù),內(nèi)部編碼為embstr類型,字符串sds和redisObject一起分配,從而只要一次內(nèi)存操作。

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

開發(fā)提示:高并發(fā)寫入場景中,在條件允許的情況下建議字符串長度控制在39字節(jié)以內(nèi),減少創(chuàng)建redisObject內(nèi)存分配次數(shù)從而提高性能。

</pre>

|

</figure>

二. 縮減鍵值對象

降低Redis內(nèi)存使用最直接的方式就是縮減鍵(key)和值(value)的長度。

  • key長度:如在設(shè)計(jì)鍵時(shí),在完整描述業(yè)務(wù)情況下,鍵值越短越好。

  • value長度:值對象縮減比較復(fù)雜,常見需求是把業(yè)務(wù)對象序列化成二進(jìn)制數(shù)組放入Redis。首先應(yīng)該在業(yè)務(wù)上精簡業(yè)務(wù)對象,去掉不必要的屬性避免存儲(chǔ)無效數(shù)據(jù)。其次在序列化工具選擇上,應(yīng)該選擇更高效的序列化工具來降低字節(jié)數(shù)組大小。以JAVA為例,內(nèi)置的序列化方式無論從速度還是壓縮比都不盡如人意,這時(shí)可以選擇更高效的序列化工具,如: protostuff,kryo等,下圖是JAVA常見序列化工具空間壓縮對比。

其中java-built-in-serializer表示JAVA內(nèi)置序列化方式,更多數(shù)據(jù)見jvm-serializers項(xiàng)目:https://github.com/eishay/jvm-serializers/wiki,其它語言也有各自對應(yīng)的高效序列化工具。

值對象除了存儲(chǔ)二進(jìn)制數(shù)據(jù)之外,通常還會(huì)使用通用格式存儲(chǔ)數(shù)據(jù)比如:json,xml等作為字符串存儲(chǔ)在Redis中。這種方式優(yōu)點(diǎn)是方便調(diào)試和跨語言,但是同樣的數(shù)據(jù)相比字節(jié)數(shù)組所需的空間更大,在內(nèi)存緊張的情況下,可以使用通用壓縮算法壓縮json,xml后再存入Redis,從而降低內(nèi)存占用,例如使用GZIP壓縮后的json可降低約60%的空間。

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

開發(fā)提示:當(dāng)頻繁壓縮解壓json等文本數(shù)據(jù)時(shí),開發(fā)人員需要考慮壓縮速度和計(jì)算開銷成本,這里推薦使用google的Snappy壓縮工具,在特定的壓縮率情況下效率遠(yuǎn)遠(yuǎn)高于GZIP等傳統(tǒng)壓縮工具,且支持所有主流語言環(huán)境。

</pre>

|

</figure>

三. 共享對象池

對象共享池指Redis內(nèi)部維護(hù)[0-9999]的整數(shù)對象池。創(chuàng)建大量的整數(shù)類型redisObject存在內(nèi)存開銷,每個(gè)redisObject內(nèi)部結(jié)構(gòu)至少占16字節(jié),甚至超過了整數(shù)自身空間消耗。所以Redis內(nèi)存維護(hù)一個(gè)[0-9999]的整數(shù)對象池,用于節(jié)約內(nèi)存。 除了整數(shù)值對象,其他類型如list,hash,set,zset內(nèi)部元素也可以使用整數(shù)對象池。因此開發(fā)中在滿足需求的前提下,盡量使用整數(shù)對象以節(jié)省內(nèi)存。
整數(shù)對象池在Redis中通過變量REDIS_SHARED_INTEGERS定義,不能通過配置修改。可以通過object refcount 命令查看對象引用數(shù)驗(yàn)證是否啟用整數(shù)對象池技術(shù),如下:

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

5

6

7

8

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

redis> set foo 100

OK

redis> object refcount foo

(integer) 2

redis> set bar 100

OK

redis> object refcount bar

(integer) 3

</pre>

|

</figure>

設(shè)置鍵foo等于100時(shí),直接使用共享池內(nèi)整數(shù)對象,因此引用數(shù)是2,再設(shè)置鍵bar等于100時(shí),引用數(shù)又變?yōu)?,如下圖所示。

使用整數(shù)對象池究竟能降低多少內(nèi)存?讓我們通過測試來對比對象池的內(nèi)存優(yōu)化效果,如下表所示。

操作說明 是否對象共享 key大小 value大小 used_mem used_memory_rss
插入200萬 20字節(jié) [0-9999]整數(shù) 199.91MB 205.28MB
插入200萬 20字節(jié) [0-9999]整數(shù) 138.87MB 143.28MB

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

注意本文所有測試環(huán)境都保持一致,信息如下:

服務(wù)器信息: cpu=Intel-Xeon E5606@2.13GHz memory=32GB

Redis版本:Redis server v=3.0.7 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64

</pre>

|

</figure>

使用共享對象池后,相同的數(shù)據(jù)內(nèi)存使用降低30%以上。可見當(dāng)數(shù)據(jù)大量使用[0-9999]的整數(shù)時(shí),共享對象池可以節(jié)約大量內(nèi)存。需要注意的是對象池并不是只要存儲(chǔ)[0-9999]的整數(shù)就可以工作。當(dāng)設(shè)置maxmemory并啟用LRU相關(guān)淘汰策略如:volatile-lru,allkeys-lru時(shí),Redis禁止使用共享對象池,測試命令如下:

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

redis> set key:1 99

OK //設(shè)置key:1=99

redis> object refcount key:1

(integer) 2 //使用了對象共享,引用數(shù)為2

redis> config set maxmemory-policy volatile-lru

OK //開啟LRU淘汰策略

redis> set key:2 99

OK //設(shè)置key:2=99

redis> object refcount key:2

(integer) 3 //使用了對象共享,引用數(shù)變?yōu)?

redis> config set maxmemory 1GB

OK //設(shè)置最大可用內(nèi)存

redis> set key:3 99

OK //設(shè)置key:3=99

redis> object refcount key:3

(integer) 1 //未使用對象共享,引用數(shù)為1

redis> config set maxmemory-policy volatile-ttl

OK //設(shè)置非LRU淘汰策略

redis> set key:4 99

OK //設(shè)置key:4=99

redis> object refcount key:4

(integer) 4 //又可以使用對象共享,引用數(shù)變?yōu)?

</pre>

|

</figure>

為什么開啟maxmemory和LRU淘汰策略后對象池?zé)o效?

LRU算法需要獲取對象最后被訪問時(shí)間,以便淘汰最長未訪問數(shù)據(jù),每個(gè)對象最后訪問時(shí)間存儲(chǔ)在redisObject對象的lru字段。對象共享意味著多個(gè)引用共享同一個(gè)redisObject,這時(shí)lru字段也會(huì)被共享,導(dǎo)致無法獲取每個(gè)對象的最后訪問時(shí)間。如果沒有設(shè)置maxmemory,直到內(nèi)存被用盡Redis也不會(huì)觸發(fā)內(nèi)存回收,所以共享對象池可以正常工作。

綜上所述,共享對象池與maxmemory+LRU策略沖突,使用時(shí)需要注意。 對于ziplist編碼的值對象,即使內(nèi)部數(shù)據(jù)為整數(shù)也無法使用共享對象池,因?yàn)閦iplist使用壓縮且內(nèi)存連續(xù)的結(jié)構(gòu),對象共享判斷成本過高,ziplist編碼細(xì)節(jié)后面內(nèi)容詳細(xì)說明。

為什么只有整數(shù)對象池?

首先整數(shù)對象池復(fù)用的幾率最大,其次對象共享的一個(gè)關(guān)鍵操作就是判斷相等性,Redis之所以只有整數(shù)對象池,是因?yàn)檎麛?shù)比較算法時(shí)間復(fù)雜度為O(1),只保留一萬個(gè)整數(shù)為了防止對象池浪費(fèi)。如果是字符串判斷相等性,時(shí)間復(fù)雜度變?yōu)镺(n),特別是長字符串更消耗性能(浮點(diǎn)數(shù)在Redis內(nèi)部使用字符串存儲(chǔ))。對于更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)如hash,list等,相等性判斷需要O(n2)。對于單線程的Redis來說,這樣的開銷顯然不合理,因此Redis只保留整數(shù)共享對象池。

四. 字符串優(yōu)化

字符串對象是Redis內(nèi)部最常用的數(shù)據(jù)類型。所有的鍵都是字符串類型, 值對象數(shù)據(jù)除了整數(shù)之外都使用字符串存儲(chǔ)。比如執(zhí)行命令:lpush cache:type “redis” “memcache” “tair” “l(fā)evelDB” ,Redis首先創(chuàng)建”cache:type”鍵字符串,然后創(chuàng)建鏈表對象,鏈表對象內(nèi)再包含四個(gè)字符串對象,排除Redis內(nèi)部用到的字符串對象之外至少創(chuàng)建5個(gè)字符串對象。可見字符串對象在Redis內(nèi)部使用非常廣泛,因此深刻理解Redis字符串對于內(nèi)存優(yōu)化非常有幫助:

1.字符串結(jié)構(gòu)

Redis沒有采用原生C語言的字符串類型而是自己實(shí)現(xiàn)了字符串結(jié)構(gòu),內(nèi)部簡單動(dòng)態(tài)字符串(simple dynamic string),簡稱SDS。結(jié)構(gòu)下圖所示。

Redis自身實(shí)現(xiàn)的字符串結(jié)構(gòu)有如下特點(diǎn):

  • O(1)時(shí)間復(fù)雜度獲取:字符串長度,已用長度,未用長度。
  • 可用于保存字節(jié)數(shù)組,支持安全的二進(jìn)制數(shù)據(jù)存儲(chǔ)。
  • 內(nèi)部實(shí)現(xiàn)空間預(yù)分配機(jī)制,降低內(nèi)存再分配次數(shù)。
  • 惰性刪除機(jī)制,字符串縮減后的空間不釋放,作為預(yù)分配空間保留。

2.預(yù)分配機(jī)制

因?yàn)樽址?SDS)存在預(yù)分配機(jī)制,日常開發(fā)中要小心預(yù)分配帶來的內(nèi)存浪費(fèi),例如下表的測試用例。

表:字符串內(nèi)存預(yù)分配測試

階段 數(shù)據(jù)量 操作說明 命令 key大小 value大小 used_mem used_memory_rss mem_fragmentation_ratio
階段1 200w 新插入200w數(shù)據(jù) set 20字節(jié) 60字節(jié) 321.98MB 331.44MB 1.02
階段2 200w 在階段1上每個(gè)對象追加60字節(jié)數(shù)據(jù) append 20字節(jié) 60字節(jié) 657.67MB 752.80MB 1.14
階段3 200w 重新插入200w數(shù)據(jù) set 20字節(jié) 120字節(jié) 474.56MB 482.45MB 1.02

從測試數(shù)據(jù)可以看出,同樣的數(shù)據(jù)追加后內(nèi)存消耗非常嚴(yán)重,下面我們結(jié)合圖來分析這一現(xiàn)象。階段1每個(gè)字符串對象空間占用如下圖所示。

階段1插入新的字符串后,free字段保留空間為0,總占用空間=實(shí)際占用空間+1字節(jié),最后1字節(jié)保存‘\0’標(biāo)示結(jié)尾,這里忽略int類型len和free字段消耗的8字節(jié)。在階段1原有字符串上追加60字節(jié)數(shù)據(jù)空間占用如下圖所示。

追加操作后字符串對象預(yù)分配了一倍容量作為預(yù)留空間,而且大量追加操作需要內(nèi)存重新分配,造成內(nèi)存碎片率(mem_fragmentation_ratio)上升。直接插入與階段2相同數(shù)據(jù)的空間占用,如下圖所示。

階段3直接插入同等數(shù)據(jù)后,相比階段2節(jié)省了每個(gè)字符串對象預(yù)分配的空間,同時(shí)降低了碎片率。
字符串之所以采用預(yù)分配的方式是防止修改操作需要不斷重分配內(nèi)存和字節(jié)數(shù)據(jù)拷貝。但同樣也會(huì)造成內(nèi)存的浪費(fèi)。字符串預(yù)分配每次并不都是翻倍擴(kuò)容,空間預(yù)分配規(guī)則如下:

    1. 第一次創(chuàng)建len屬性等于數(shù)據(jù)實(shí)際大小,free等于0,不做預(yù)分配。
    1. 修改后如果已有free空間不夠且數(shù)據(jù)小于1M,每次預(yù)分配一倍容量。如原有l(wèi)en=60byte,free=0,再追加60byte,預(yù)分配120byte,總占用空間:60byte+60byte+120byte+1byte。
    1. 修改后如果已有free空間不夠且數(shù)據(jù)大于1MB,每次預(yù)分配1MB數(shù)據(jù)。如原有l(wèi)en=30MB,free=0,當(dāng)再追加100byte ,預(yù)分配1MB,總占用空間:1MB+100byte+1MB+1byte。
      開發(fā)提示:盡量減少字符串頻繁修改操作如append,setrange, 改為直接使用set修改字符串,降低預(yù)分配帶來的內(nèi)存浪費(fèi)和內(nèi)存碎片化。

3.字符串重構(gòu)

字符串重構(gòu):指不一定把每份數(shù)據(jù)作為字符串整體存儲(chǔ),像json這樣的數(shù)據(jù)可以使用hash結(jié)構(gòu),使用二級(jí)結(jié)構(gòu)存儲(chǔ)也能幫我們節(jié)省內(nèi)存。同時(shí)可以使用hmget,hmset命令支持字段的部分讀取修改,而不用每次整體存取。例如下面的json數(shù)據(jù):

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

5

6

7

8

9

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

{

"vid": "413368768",

"title": "搜狐屌絲男士",

"videoAlbumPic": "http://photocdn.sohu.com/60160518/vrsa_ver8400079_ae433_pic26.jpg",

"pid": "6494271",

"type": "1024",

"playlist": "6494271",

"playTime": "468"

}

</pre>

|

</figure>

分別使用字符串和hash結(jié)構(gòu)測試內(nèi)存表現(xiàn),如下表所示。

表:測試內(nèi)存表現(xiàn)

數(shù)據(jù)量 key 存儲(chǔ)類型 value 配置 used_mem
200W 20字節(jié) string json字符串 默認(rèn) 612.62M
200W 20字節(jié) hash key-value對 默認(rèn) 默認(rèn) 1.88GB
200W 20字節(jié) hash key-value對 hash-max-ziplist-value:66 535.60M

根據(jù)測試結(jié)構(gòu),第一次默認(rèn)配置下使用hash類型,內(nèi)存消耗不但沒有降低反而比字符串存儲(chǔ)多出2倍,而調(diào)整hash-max-ziplist-value=66之后內(nèi)存降低為535.60M。因?yàn)閖son的videoAlbumPic屬性長度是65,而hash-max-ziplist-value默認(rèn)值是64,Redis采用hashtable編碼方式,反而消耗了大量內(nèi)存。調(diào)整配置后hash類型內(nèi)部編碼方式變?yōu)閦iplist,相比字符串更省內(nèi)存且支持屬性的部分操作。下一節(jié)將具體介紹ziplist編碼優(yōu)化細(xì)節(jié)。

五. 編碼優(yōu)化

1.了解編碼

Redis對外提供了string,list,hash,set,zet等類型,但是Redis內(nèi)部針對不同類型存在編碼的概念,所謂編碼就是具體使用哪種底層數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)。編碼不同將直接影響數(shù)據(jù)的內(nèi)存占用和讀寫效率。使用object encoding {key}命令獲取編碼類型。如下:

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

5

6

7

8

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

redis> set str:1 hello

OK

redis> object encoding str:1

"embstr" // embstr編碼字符串

redis> lpush list:1 1 2 3

(integer) 3

redis> object encoding list:1

"ziplist" // ziplist編碼列表

</pre>

|

</figure>

Redis針對每種數(shù)據(jù)類型(type)可以采用至少兩種編碼方式來實(shí)現(xiàn),下表表示type和encoding的對應(yīng)關(guān)系。

表:type和encoding對應(yīng)關(guān)系表

類型 編碼方式 數(shù)據(jù)結(jié)構(gòu)
string raw

embstr
int | 動(dòng)態(tài)字符串編碼
優(yōu)化內(nèi)存分配的字符串編碼
整數(shù)編碼 |
| hash | hashtable
ziplist | 散列表編碼
壓縮列表編碼 |
| list | linkedlist
ziplist
quicklist
| 雙向鏈表編碼
壓縮列表編碼
3.2版本新的列表編碼 |
| set | hashtable
intset | 散列表編碼
整數(shù)集合編碼 |
| zset | skiplist
ziplist | 跳躍表編碼
壓縮列表編碼 |

了解編碼和類型對應(yīng)關(guān)系之后,我們不禁疑惑Redis為什么需要對一種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)多種編碼方式?
主要原因是Redis作者想通過不同編碼實(shí)現(xiàn)效率和空間的平衡。比如當(dāng)我們的存儲(chǔ)只有10個(gè)元素的列表,當(dāng)使用雙向鏈表數(shù)據(jù)結(jié)構(gòu)時(shí),必然需要維護(hù)大量的內(nèi)部字段如每個(gè)元素需要:前置指針,后置指針,數(shù)據(jù)指針等,造成空間浪費(fèi),如果采用連續(xù)內(nèi)存結(jié)構(gòu)的壓縮列表(ziplist),將會(huì)節(jié)省大量內(nèi)存,而由于數(shù)據(jù)長度較小,存取操作時(shí)間復(fù)雜度即使為O(n2)性能也可滿足需求。

Redis內(nèi)存優(yōu)化

2.控制編碼類型

編碼類型轉(zhuǎn)換在Redis寫入數(shù)據(jù)時(shí)自動(dòng)完成,這個(gè)轉(zhuǎn)換過程是不可逆的,轉(zhuǎn)換規(guī)則只能從小內(nèi)存編碼向大內(nèi)存編碼轉(zhuǎn)換。例如:

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

redis> lpush list:1 a b c d

(integer) 4 //存儲(chǔ)4個(gè)元素

redis> object encoding list:1

"ziplist" //采用ziplist壓縮列表編碼

redis> config set list-max-ziplist-entries 4

OK //設(shè)置列表類型ziplist編碼最大允許4個(gè)元素

redis> lpush list:1 e

(integer) 5 //寫入第5個(gè)元素e

redis> object encoding list:1

"linkedlist" //編碼類型轉(zhuǎn)換為鏈表

redis> rpop list:1

"a" //彈出元素a

redis> llen list:1

(integer) 4 // 列表此時(shí)有4個(gè)元素

redis> object encoding list:1

"linkedlist" //編碼類型依然為鏈表,未做編碼回退

</pre>

|

</figure>

以上命令體現(xiàn)了list類型編碼的轉(zhuǎn)換過程,其中Redis之所以不支持編碼回退,主要是數(shù)據(jù)增刪頻繁時(shí),數(shù)據(jù)向壓縮編碼轉(zhuǎn)換非常消耗CPU,得不償失。以上示例用到了list-max-ziplist-entries參數(shù),這個(gè)參數(shù)用來決定列表長度在多少范圍內(nèi)使用ziplist編碼。當(dāng)然還有其它參數(shù)控制各種數(shù)據(jù)類型的編碼,如下表所示:

表:hash,list,set,zset內(nèi)部編碼配置

類型 編碼 決定條件
hash ziplist 滿足所有條件:

value最大空間(字節(jié))<=hash-max-ziplist-value
field個(gè)數(shù)<=hash-max-ziplist-entries |
| 同上 | hashtable | 滿足任意條件:
value最大空間(字節(jié))>hash-max-ziplist-value
field個(gè)數(shù)>hash-max-ziplist-entries |
| list | ziplist | ziplist 滿足所有條件:
value最大空間(字節(jié))<=list-max-ziplist-value
鏈表長度<=list-max-ziplist-entries |
| 同上 | linkedlist | 滿足任意條件
value最大空間(字節(jié))>list-max-ziplist-value
鏈表長度>list-max-ziplist-entries |
| 同上 | quicklist | 3.2版本新編碼:
廢棄list-max-ziplist-entries和list-max-ziplist-entries配置
使用新配置:
list-max-ziplist-size:表示最大壓縮空間或長度,最大空間使用[-5-1]范圍配置,默認(rèn)-2表示8KB,正整數(shù)表示最大壓縮長度
list-compress-depth:表示最大壓縮深度,默認(rèn)=0不壓縮 |
| set | intset | 滿足所有條件:
元素必須為整數(shù)
集合長度<=set-max-intset-entries |
| 同上 | hashtable | 滿足任意條件
元素非整數(shù)類型
集合長度>hash-max-ziplist-entries |
| zset | ziplist | 滿足所有條件:
value最大空間(字節(jié))<=zset-max-ziplist-value
有序集合長度<=zset-max-ziplist-entries |
| 同上 | skiplist | 滿足任意條件:
value最大空間(字節(jié))>zset-max-ziplist-value
有序集合長度>zset-max-ziplist-entries |

掌握編碼轉(zhuǎn)換機(jī)制,對我們通過編碼來優(yōu)化內(nèi)存使用非常有幫助。下面以hash類型為例,介紹編碼轉(zhuǎn)換的運(yùn)行流程,如下圖所示。

理解編碼轉(zhuǎn)換流程和相關(guān)配置之后,可以使用config set命令設(shè)置編碼相關(guān)參數(shù)來滿足使用壓縮編碼的條件。對于已經(jīng)采用非壓縮編碼類型的數(shù)據(jù)如hashtable,linkedlist等,設(shè)置參數(shù)后即使數(shù)據(jù)滿足壓縮編碼條件,Redis也不會(huì)做轉(zhuǎn)換,需要重啟Redis重新加載數(shù)據(jù)才能完成轉(zhuǎn)換。

3.ziplist編碼

ziplist編碼主要目的是為了節(jié)約內(nèi)存,因此所有數(shù)據(jù)都是采用線性連續(xù)的內(nèi)存結(jié)構(gòu)。ziplist編碼是應(yīng)用范圍最廣的一種,可以分別作為hash、list、zset類型的底層數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。首先從ziplist編碼結(jié)構(gòu)開始分析,它的內(nèi)部結(jié)構(gòu)類似這樣:<zlbytes><zltail><zllen><entry-1><entry-2><….><entry-n><zlend>。一個(gè)ziplist可以包含多個(gè)entry(元素),每個(gè)entry保存具體的數(shù)據(jù)(整數(shù)或者字節(jié)數(shù)組),內(nèi)部結(jié)構(gòu)如下圖所示。</zlend></entry-n></entry-2></entry-1></zllen></zltail></zlbytes>

ziplist結(jié)構(gòu)字段含義:

    1. zlbytes:記錄整個(gè)壓縮列表所占字節(jié)長度,方便重新調(diào)整ziplist空間。類型是int-32,長度為4字節(jié)
    1. zltail:記錄距離尾節(jié)點(diǎn)的偏移量,方便尾節(jié)點(diǎn)彈出操作。類型是int-32,長度為4字節(jié)
    1. zllen:記錄壓縮鏈表節(jié)點(diǎn)數(shù)量,當(dāng)長度超過216-2時(shí)需要遍歷整個(gè)列表獲取長度,一般很少見。類型是int-16,長度為2字節(jié)
    1. entry:記錄具體的節(jié)點(diǎn),長度根據(jù)實(shí)際存儲(chǔ)的數(shù)據(jù)而定。
    • a) prev_entry_bytes_length:記錄前一個(gè)節(jié)點(diǎn)所占空間,用于快速定位上一個(gè)節(jié)點(diǎn),可實(shí)現(xiàn)列表反向迭代。
    • b) encoding:標(biāo)示當(dāng)前節(jié)點(diǎn)編碼和長度,前兩位表示編碼類型:字符串/整數(shù),其余位表示數(shù)據(jù)長度。
    • c) contents:保存節(jié)點(diǎn)的值,針對實(shí)際數(shù)據(jù)長度做內(nèi)存占用優(yōu)化。
    1. zlend:記錄列表結(jié)尾,占用一個(gè)字節(jié)。

根據(jù)以上對ziplist字段說明,可以分析出該數(shù)據(jù)結(jié)構(gòu)特點(diǎn)如下:

    1. 內(nèi)部表現(xiàn)為數(shù)據(jù)緊湊排列的一塊連續(xù)內(nèi)存數(shù)組。
    1. 可以模擬雙向鏈表結(jié)構(gòu),以O(shè)(1)時(shí)間復(fù)雜度入隊(duì)和出隊(duì)。
    1. 新增刪除操作涉及內(nèi)存重新分配或釋放,加大了操作的復(fù)雜性。
    1. 讀寫操作涉及復(fù)雜的指針移動(dòng),最壞時(shí)間復(fù)雜度為O(n2)。
    1. 適合存儲(chǔ)小對象和長度有限的數(shù)據(jù)。

下面通過測試展示ziplist編碼在不同類型中內(nèi)存和速度的表現(xiàn),如下表所示。

表:ziplist在hash,list,zset內(nèi)存和速度測試

類型 數(shù)據(jù)量 key總數(shù)量 長度 value大小 普通編碼內(nèi)存量/平均耗時(shí) 壓縮編碼內(nèi)存量/平均耗時(shí) 內(nèi)存降低比例 耗時(shí)增長倍數(shù)
hash 100萬 1千 1千 36字節(jié) 103.37M/0.84微秒 43.83M/13.24微秒 57.5% 15倍
list 100萬 1千 1千 36字節(jié) 92.46M/2.04微秒 39.92M/5.45微秒 56.8% 2.5倍
zset 100萬 1千 1千 36字節(jié) 151.84M/1.85微秒 43.83M/77.88微秒 71% 42倍

測試數(shù)據(jù)采用100W個(gè)36字節(jié)數(shù)據(jù),劃分為1000個(gè)鍵,每個(gè)類型長度統(tǒng)一為1000。從測試結(jié)果可以看出:

  1. 使用ziplist可以分別作為hash,list,zset數(shù)據(jù)類型實(shí)現(xiàn)。
  2. 使用ziplist編碼類型可以大幅降低內(nèi)存占用。
  3. ziplist實(shí)現(xiàn)的數(shù)據(jù)類型相比原生結(jié)構(gòu),命令操作更加耗時(shí),不同類型耗時(shí)排序:list < hash < zset。
    ziplist壓縮編碼的性能表現(xiàn)跟值長度和元素個(gè)數(shù)密切相關(guān),正因?yàn)槿绱薘edis提供了{(lán)type}-max-ziplist-value和{type}-max-ziplist-entries相關(guān)參數(shù)來做控制ziplist編碼轉(zhuǎn)換。最后再次強(qiáng)調(diào)使用ziplist壓縮編碼的原則:追求空間和時(shí)間的平衡。
    開發(fā)提示:
    1)針對性能要求較高的場景使用ziplist,建議長度不要超過1000,每個(gè)元素大小控制在512字節(jié)以內(nèi)。
    2)命令平均耗時(shí)使用info Commandstats命令獲取,包含每個(gè)命令調(diào)用次數(shù),總耗時(shí),平均耗時(shí),單位微秒。

4.intset編碼

intset編碼是集合(set)類型編碼的一種,內(nèi)部表現(xiàn)為存儲(chǔ)有序,不重復(fù)的整數(shù)集。當(dāng)集合只包含整數(shù)且長度不超過set-max-intset-entries配置時(shí)被啟用。執(zhí)行以下命令查看intset表現(xiàn):

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

2

3

4

5

6

7

8

9

10

11

12

13

14

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

127.0.0.1:6379> sadd set:test 3 4 2 6 8 9 2

(integer) 6 //亂序?qū)懭?個(gè)整數(shù)

127.0.0.1:6379> object encoding set:test

"intset" //使用intset編碼

127.0.0.1:6379> smembers set:test

"2" "3" "4" "6" "8" "9" // 排序輸出整數(shù)結(jié)合

redis> config set set-max-intset-entries 6

OK //設(shè)置intset最大允許整數(shù)長度

redis> sadd set:test 5

(integer) 1 //寫入第7個(gè)整數(shù) 5

redis> object encoding set:test

"hashtable" // 編碼變?yōu)閔ashtable

redis> smembers set:test

"8" "3" "5" "9" "4" "2" "6" //亂序輸出

</pre>

|

</figure>

以上命令可以看出intset對寫入整數(shù)進(jìn)行排序,通過O(log(n))時(shí)間復(fù)雜度實(shí)現(xiàn)查找和去重操作,intset編碼結(jié)構(gòu)如下圖所示。

intset的字段結(jié)構(gòu)含義:

  1. encoding:整數(shù)表示類型,根據(jù)集合內(nèi)最長整數(shù)值確定類型,整數(shù)類型劃分三種:int-16,int-32,int-64。
  2. length:表示集合元素個(gè)數(shù)。
  3. contents:整數(shù)數(shù)組,按從小到大順序保存。
    intset保存的整數(shù)類型根據(jù)長度劃分,當(dāng)保存的整數(shù)超出當(dāng)前類型時(shí),將會(huì)觸發(fā)自動(dòng)升級(jí)操作且升級(jí)后不再做回退。升級(jí)操作將會(huì)導(dǎo)致重新申請內(nèi)存空間,把原有數(shù)據(jù)按轉(zhuǎn)換類型后拷貝到新數(shù)組。
    開發(fā)提示:使用intset編碼的集合時(shí),盡量保持整數(shù)范圍一致,如都在int-16范圍內(nèi)。防止個(gè)別大整數(shù)觸發(fā)集合升級(jí)操作,產(chǎn)生內(nèi)存浪費(fèi)。
    下面通過測試查看ziplist編碼的集合內(nèi)存和速度表現(xiàn),如下表所示。

表:ziplist編碼在set下內(nèi)存和速度表現(xiàn)

數(shù)據(jù)量 key大小 value大小 編碼 集合長度 內(nèi)存量 內(nèi)存降低比例 平均耗時(shí)
100w 20byte 7字節(jié) hashtable 1千 61.97MB 0.78毫秒
100w 20byte 7字節(jié) intset 1千 4.77MB 92.6% 0.51毫秒
100w 20byte 7字節(jié) ziplist 1千 8.67MB 86.2% 13.12毫秒

根據(jù)以上測試結(jié)果發(fā)現(xiàn)intset表現(xiàn)非常好,同樣的數(shù)據(jù)內(nèi)存占用只有不到hashtable編碼的十分之一。intset數(shù)據(jù)結(jié)構(gòu)插入命令復(fù)雜度為O(n),查詢命令為O(log(n)),由于整數(shù)占用空間非常小,所以在集合長度可控的基礎(chǔ)上,寫入命令執(zhí)行速度也會(huì)非常快,因此當(dāng)使用整數(shù)集合時(shí)盡量使用intset編碼。上表測試第三行把ziplist-hash類型也放入其中,主要因?yàn)閕ntset編碼必須存儲(chǔ)整數(shù),當(dāng)集合內(nèi)保存非整數(shù)數(shù)據(jù)時(shí),無法使用intset實(shí)現(xiàn)內(nèi)存優(yōu)化。這時(shí)可以使用ziplist-hash類型對象模擬集合類型,hash的field當(dāng)作集合中的元素,value設(shè)置為1字節(jié)占位符即可。使用ziplist編碼的hash類型依然比使用hashtable編碼的集合節(jié)省大量內(nèi)存。

六 控制key的數(shù)量

當(dāng)使用Redis存儲(chǔ)大量數(shù)據(jù)時(shí),通常會(huì)存在大量鍵,過多的鍵同樣會(huì)消耗大量內(nèi)存。Redis本質(zhì)是一個(gè)數(shù)據(jù)結(jié)構(gòu)服務(wù)器,它為我們提供多種數(shù)據(jù)結(jié)構(gòu),如hash,list,set,zset 等結(jié)構(gòu)。使用Redis時(shí)不要進(jìn)入一個(gè)誤區(qū),大量使用get/set這樣的API,把Redis當(dāng)成Memcached使用。對于存儲(chǔ)相同的數(shù)據(jù)內(nèi)容利用Redis的數(shù)據(jù)結(jié)構(gòu)降低外層鍵的數(shù)量,也可以節(jié)省大量內(nèi)存。如下圖所示,通過在客戶端預(yù)估鍵規(guī)模,把大量鍵分組映射到多個(gè)hash結(jié)構(gòu)中降低鍵的數(shù)量。

hash結(jié)構(gòu)降低鍵數(shù)量分析:

  • 根據(jù)鍵規(guī)模在客戶端通過分組映射到一組hash對象中,如存在100萬個(gè)鍵,可以映射到1000個(gè)hash中,每個(gè)hash保存1000個(gè)元素。
  • hash的field可用于記錄原始key字符串,方便哈希查找。
  • hash的value保存原始值對象,確保不要超過hash-max-ziplist-value限制。
    下面測試這種優(yōu)化技巧的內(nèi)存表現(xiàn),如下表所示。

表:hash分組控制鍵規(guī)模測試

數(shù)據(jù)量 key大小 value大小 string類型占用內(nèi)存 hash-ziplist類型占用內(nèi)存 內(nèi)存降低比例 string:set平均耗時(shí) hash:hset平均耗時(shí)
200w 20byte 512byte 1392.64MB 1000.97MB 28.1% 2.13微秒 21.28微秒
200w 20byte 200byte 596.62MB 399.38MB 33.1% 1.49微秒 16.08微秒
200w 20byte 100byte 382.99MB 211.88MB 44.6% 1.30微秒 14.92微秒
200w 20byte 50byte 291.46MB 110.32MB 62.1% 1.28微秒 13.48微秒
200w 20byte 20byte 246.40MB 55.63MB 77.4% 1.10微秒 13.21微秒
200w 20byte 5byte 199.93MB 24.42MB 87.7% 1.10微秒 13.06微秒

通過這個(gè)測試數(shù)據(jù),可以說明:

  • 同樣的數(shù)據(jù)使用ziplist編碼的hash類型存儲(chǔ)比string類型節(jié)約內(nèi)存
  • 節(jié)省內(nèi)存量隨著value空間的減少,越來越明顯。
  • hash-ziplist類型比string類型寫入耗時(shí),但隨著value空間的減少,耗時(shí)逐漸降低。
    使用hash重構(gòu)后節(jié)省內(nèi)存量效果非常明顯,特變對于存儲(chǔ)小對象的場景,內(nèi)存只有不到原來的1/5。下面分析這種內(nèi)存優(yōu)化技巧的關(guān)鍵點(diǎn):
    1. hash類型節(jié)省內(nèi)存的原理是使用ziplist編碼,如果使用hashtable編碼方式反而會(huì)增加內(nèi)存消耗。
    2. ziplist長度需要控制在1000以內(nèi),否則由于存取操作時(shí)間復(fù)雜度在O(n)到O(n2)之間,長列表會(huì)導(dǎo)致CPU消耗嚴(yán)重,得不償失。
    3. ziplist適合存儲(chǔ)的小對象,對于大對象不但內(nèi)存優(yōu)化效果不明顯還會(huì)增加命令操作耗時(shí)。
    4. 需要預(yù)估鍵的規(guī)模,從而確定每個(gè)hash結(jié)構(gòu)需要存儲(chǔ)的元素?cái)?shù)量。
    5. 根據(jù)hash長度和元素大小,調(diào)整hash-max-ziplist-entries和hash-max-ziplist-value參數(shù),確保hash類型使用ziplist編碼。

關(guān)于hash鍵和field鍵的設(shè)計(jì):

  1. 當(dāng)鍵離散度較高時(shí),可以按字符串位截取,把后三位作為哈希的field,之前部分作為哈希的鍵。如:key=1948480 哈希key=group:hash:1948,哈希field=480。
  2. 當(dāng)鍵離散度較低時(shí),可以使用哈希算法打散鍵,如:使用crc32(key)&10000函數(shù)把所有的鍵映射到“0-9999”整數(shù)范圍內(nèi),哈希field存儲(chǔ)鍵的原始值。
  3. 盡量減少hash鍵和field的長度,如使用部分鍵內(nèi)容。

使用hash結(jié)構(gòu)控制鍵的規(guī)模雖然可以大幅降低內(nèi)存,但同樣會(huì)帶來問題,需要提前做好規(guī)避處理。如下:

  1. 客戶端需要預(yù)估鍵的規(guī)模并設(shè)計(jì)hash分組規(guī)則,加重客戶端開發(fā)成本。
  2. hash重構(gòu)后所有的鍵無法再使用超時(shí)(expire)和LRU淘汰機(jī)制自動(dòng)刪除,需要手動(dòng)維護(hù)刪除。
  3. 對于大對象,如1KB以上的對象。使用hash-ziplist結(jié)構(gòu)控制鍵數(shù)量。
    不過瑕不掩瑜,對于大量小對象的存儲(chǔ)場景,非常適合使用ziplist編碼的hash類型控制鍵的規(guī)模來降低內(nèi)存。

<figure class="highlight plain" style="display: block; margin: 20px 0px; overflow: auto; padding: 15px; font-size: 13px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6;">

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px 20px 1px 1px; color: rgb(102, 102, 102); background: rgb(247, 247, 247); line-height: 1.6; border: none; text-align: right;">

1

</pre>

|

<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 1px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none;">

開發(fā)提示:使用ziplist+hash優(yōu)化keys后,如果想使用超時(shí)刪除功能,開發(fā)人員可以存儲(chǔ)每個(gè)對象寫入的時(shí)間,再通過定時(shí)任務(wù)使用hscan命令掃描數(shù)據(jù),找出hash內(nèi)超時(shí)的數(shù)據(jù)項(xiàng)刪除即可。

</pre>

|

</figure>

本文主要講解Redis內(nèi)存優(yōu)化技巧,Redis的數(shù)據(jù)特性是”ALL IN MEMORY”,優(yōu)化內(nèi)存將變得非常重要。對于內(nèi)存優(yōu)化建議讀者先要掌握Redis內(nèi)存存儲(chǔ)的特性比如字符串,壓縮編碼,整數(shù)集合等,再根據(jù)數(shù)據(jù)規(guī)模和所用命令需求去調(diào)整,從而達(dá)到空間和效率的最佳平衡。建議使用Redis存儲(chǔ)大量數(shù)據(jù)時(shí),把內(nèi)存優(yōu)化環(huán)節(jié)加入到前期設(shè)計(jì)階段,否則數(shù)據(jù)大幅增長后,開發(fā)人員需要面對重新優(yōu)化內(nèi)存所帶來開發(fā)和數(shù)據(jù)遷移的雙重成本。當(dāng)Redis內(nèi)存不足時(shí),首先考慮的問題不是加機(jī)器做水平擴(kuò)展,應(yīng)該先嘗試做內(nèi)存優(yōu)化。當(dāng)遇到瓶頸時(shí),再去考慮水平擴(kuò)展。即使對于集群化方案,垂直層面優(yōu)化也同樣重要,避免不必要的資源浪費(fèi)和集群化后的管理成本。

目前京東china-pub已經(jīng)預(yù)售,歡迎關(guān)注。

<footer class="post-footer" style="display: block; color: rgb(85, 85, 85); font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">

Redis的Linux系統(tǒng)優(yōu)化

《Redis開發(fā)與運(yùn)維》勘誤列表

</footer>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來自《Redis開發(fā)與運(yùn)維》一書第八章,如轉(zhuǎn)載請聲明。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 18,910評(píng)論 2 29
  • 1、特殊編碼: 自從Redis 2.2之后,很多數(shù)據(jù)類型都可以通過特殊編碼的方式來進(jìn)行存儲(chǔ)空間的優(yōu)化。其中,Has...
    六月星空2011閱讀 253評(píng)論 0 0
  • Redis 內(nèi)存優(yōu)化 官網(wǎng)文檔 翻譯 使用特殊編碼方式存儲(chǔ)聚合數(shù)據(jù)類型 從 Redis 2.2 開始,很多數(shù)據(jù)結(jié)構(gòu)...
    Cajesse佳澤閱讀 383評(píng)論 0 0
  • 一、一次string轉(zhuǎn)化為hash的優(yōu)化 1. 場景: 用戶id: userId, 用戶微博數(shù)量:weiboCou...
    volcano822閱讀 541評(píng)論 0 0
  • 不讀書能改變命運(yùn)嗎?這個(gè)問題的答案可以很好的回答“讀書能改變命運(yùn)嗎?” 背景 讀簡書文章《你還相信讀書改變命運(yùn)嗎?...
    許瑞光閱讀 161評(píng)論 0 0