天天看點

Redis資料結構

keys *:檢視所有的鍵 O(n)

dbsize:鍵總數 O(1)

exists key :檢查鍵是否存在

del key:删除鍵

expire key seconds:鍵過期

ttl : 傳回鍵的剩餘時間 (

>=0: 剩餘時間

-1:沒設定過期時間

-2:鍵不存在

)

type key:鍵的類型

object encoding:查詢内部編碼

每種資料結構都有兩種以上的内部編碼實作,好處:

可以改進内部編碼,而對外的資料結構和指令沒有影響

多種内部編碼實作可以在不同場景下發揮各自的優勢

為什麼單線程還能這麼快?

純記憶體通路

非阻塞I/O,redis使用epoll作為I/O多路複用技術的實作

單線程避免了線程切換和競态産生的消耗

缺點:如果某個指令執行時間過長,會造成其他指令的阻塞。

字元串類型的值可以是普通字元串,也可以是複雜字元串(Json, Xml), 數字(整數、浮點數),二進制的圖檔、音頻、視訊,但值最大不能超過512MB

set key value [ex seconds] [px milliseconds] [nx | xx]

ex seconds : 為鍵設定秒級過期時間

px milliseconds : 為鍵設定毫秒級過期時間

nx : 鍵不存在才設定成功,用于添加

xx : 鍵存在才設定成功,用于更新

get key

mset key value [key value ……]

mget key [key……]

incr key 自增

值不是整數,傳回錯誤

值是整數,傳回自增後的結果

鍵不存在,按照值為0自增,傳回結果為1

decr、incrby(自增指定數字)、decrby、incrbyfloat

append key value 向字元串尾部追加值

strlen key 字元串長度

每個中文占用3個位元組

getset key value 設定并傳回原值

setrange key offset value 設定指定位置的字元

getrange key start end 擷取部分字元串,下标從0開始,包括右下标

有3種編碼:

int:8個位元組的長整型

embstr:小于等于39個位元組的字元串

raw:大于39個位元組的字元串

通過object encoding key 檢視内部編碼

緩存功能

計數:視訊播放數

共享session:出于分布式服務考慮,已不适用

限速:防盜刷

hset key field value 設定值

hget key field 擷取值

hdel key field 删除filed

hlen key 計算field的個數

hmget key field [filed ……]

hmset key filed value ……

hexists key field 判斷field是否存在

hkeys key 擷取所有field

hvals key 擷取所有value

hgetall key 擷取所有的f-v,如果個數過多,會造成阻塞

hincrby key field

hincrbyfloat key field

hstrlen key field 計算value的長度

有2種:

ziplist (壓縮清單):當元素個數小于hash-max-ziplist-entries配置(預設512個)、同時所有值都小于hash-max-ziplist-value配置(預設64位元組),redis使用ziplist作為内部編碼。ziplist 使用緊湊的結構實作多個元素的連續存儲,在節省記憶體方面比hashtable更加好。

hashtable (哈希表):當上述條件無法滿足時,redis使用hashtable,因為此時ziplist 的讀寫效率會下降,而hashtable 的讀寫時間複雜度為O(1)

緩存:使用者的id作為鍵字尾,多對f-v對應每個使用者的屬性

一個清單最多可以存儲232-1 個元素,清單中的元素是有序的且可以重複的

lpush key value 從左插入元素

rpush key value 從右插入元素

lrange 0 -1 從左到右擷取清單中的所有元素

linsert key before | after pivot value 從清單中找到等于pivot的元素,插入

lrange key start end 擷取指定索引範圍的所有元素,索引下标:從左到右是0 - N-1,從右到左是-1 - -N;lrange中的end元素包括自身。

lindex key index 擷取指定索引下标

llen key 擷取清單長度

lpop key 從左彈出元素

lrem key count value

count > 0, 從左到右,最多删除count個value值的元素

count < 0, 從右到左,最多删除count絕對值的元素

count = 0,删除所有。

ltrim key start end 按照索引範圍剪清單

lset key index newValue 修改指定索引下标的元素

blpop/brpop key timeout 阻塞彈出

key 可以多個

timeout 如果清單為空,則等待timeout時間傳回。timeout = 0時,則一直等待。

如果清單不為空,立即傳回

注意:

如果有多個鍵,則從左到右周遊鍵,有一個鍵可以傳回就立即傳回。

如果多個用戶端對同一個鍵執行blpop,則最先執行指令的用戶端傳回,其他用戶端阻塞。

ziplist:當清單元素個數小于list - max - ziplist - entries(預設512個),同時清單中每個元素的值小于list - max - ziplist - values(預設64位元組)

linkedlist:無法滿足ziplist條件,則用linkedlist

消息隊列:用rpush + blpop 實作阻塞隊列。多個消費者用戶端使用blpop阻塞“搶”清單尾部元素,多個用戶端保證了消費的負載均衡和高可用性。

文章清單:每個使用者有自己的文章清單,需要分頁展示文章清單

rpush user:1:articles 文章

lrange user:1:articles 0 9 (ps:取出前10條)

集合不允許有重複元素,且元素是無序的,不能通過索引擷取。

sadd key element[...] 添加元素

srem key element[...] 删除元素

scard key 計算元素個數

sismember key element 判斷元素是否在集合中,傳回1存在,0不存在

srandmember key 【count】 随機傳回集合中指定個數的元素

spop key 随機彈出

smembers key 擷取所有元素,可能會造成阻塞

sinter key [key...] 求交集

sunion key [key...] 求并集

sdiff key [key...] 求差集

sinterstore / sunionstore / sdiffstore destination key [key...] 求結果并儲存

intset(整數集合):元素都是整數并且元素個數小于 set - max - intset - entries (預設512個),減少記憶體的使用

hashtable(哈希表):無法滿足上述,則用hashtable

标簽:比如使用者對娛樂、體育、曆史等比較感興趣,可以得到喜歡同一個标簽的人,以及使用者的共同喜好标簽。

給使用者添加标簽和給标簽添加使用者,需要在一個事務中進行

删除使用者下的标簽和删除标簽下的使用者,也需要在一個事務中進行

計算使用者共同感興趣的标簽:sinter user:1:tage user2:tag

抽獎

沒有重複元素,給每個元素設定一個分數,作為排序的依據;提供了擷取指定元素分數和元素範圍查詢、計算成員排名等功能

zadd key score member [....] 添加成員

zcard key 計算成員個數

zscore key memeber 計算某個成員分數

zrank key memeber 從低到高排名

zrevrank key member 從高到底排名

zrem key member [....] 删除成員

zincrby key increment member 增加成員分數

zrange key start end [withscores] 傳回指定排名範圍成員

zrevrange key start end [withscores] 傳回指定排名範圍成員

zrangebyscore key min max 傳回指定分數範圍成員

zcount key min max 傳回指定分數範圍成員個數

zremrangebyrank key start end 删除指定排名範圍内的元素

zremrangebyscore key min max 删除指定分數範圍内的元素

zinterstore destination numkeys key [key...] [weights [weight ...]] [aggregate sum|min|max] 求交集

destination : 儲存到目标集合

numkeys :鍵的個數

weights:每個鍵的權重,預設是1

aggregate :聚合運算,預設是sum

zunionstore......

ziplist:元素個數小于zset - max - ziplist - entries(預設128個),值小于zset - max - ziplist - value(預設64位元組)

skiplist:不滿足上述條件

排行榜:比如對使用者上傳的視訊做排行榜,可以按照時間、播放量、贊數排行。

比如贊數:

添加使用者贊數

zadd ...

之後再獲得一個贊:zincrby ....

取消使用者贊數

zrem ...

展示擷取贊數最多的10個使用者

zrevrangebyrank ...

壓縮清單是清單鍵和哈希鍵的底層實作之一,鍵值要麼是小整數值,要麼是短字元串

壓縮清單是為了節約記憶體,是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,一個壓縮清單可以包含任意多個節點(entry),每個節點可以儲存一個位元組數組或者一個整數值

Redis資料結構

壓縮清單組成:

屬性

類型

長度

用途

zlbytes

uint32_t

4位元組

記錄整個壓縮清單占用的位元組數:進行記憶體重配置設定或者計算zlend位置時使用

zltail

記錄表尾節點距離壓縮清單的起始位址有多少位元組:可以無須周遊就确定尾節點位址

zllen

uint16_t

2位元組

記錄清單包含的節點數量:當小于65535時,節點數量;否則,隻能周遊算出數量

entryX

清單節點

不定

壓縮清單節點,節點的長度由節點内容決定

zlend

uint8_t

1位元組

特殊值OxFF(十進制255),用于标記壓縮清單的末端

節點組成:

每個節點可以儲存一個位元組數組或一個整數值:

位元組數組:

長度小于等于26 - 1的位元組數組

小于等于214 - 1

小于等于232 - 1

整數值:

4位長,0-12的無符号整數

1位元組長的有符号整數

3位元組長的有符号整數

int16_t 類型整數

int32_t 類型整數

int64_t 類型整數

previous_entry_length:以位元組為機關,可以是1位元組或5位元組,記錄前一個節點的長度

如果前一個節點的長度小于254位元組,該值為1位元組

如果前一個節點的長度大于等于254位元組,該值為5位元組,第一位元組會被設定為0xFE(254),後4個位元組表示前一節點的長度

作用:可以根據目前節點的起始位址計算前一節點的起始位址,壓縮清單從表尾到表頭的周遊操作就是這樣實作的。

encoding:記錄content屬性儲存的資料類型及長度

位元組數組:1位元組、2位元組或5位元組長,值最高位為00,01或者10

編碼

編碼長度

content屬性儲存的值

00bbbbbbb

長度小于等于26 - 1位元組的位元組數組

01bbbbbbb xxxxxxxx

長度小于等于214 - 1位元組的位元組數組

10___ aaa……

5位元組

長度小于等于232 - 1位元組的位元組數組

整數:1位元組長,值的最高位以11開頭

11000000

int16_t類型的整數

11010000

int32_t類型的整數

11100000

int64_t類型的整數

11110000

11111110

8位有符号整數

1111xxxx

沒有content屬性,本身包含了0-12的值

content:負責儲存節點的值,節點值可以是一個整數或位元組數組

連鎖更新:壓縮清單恰有好多個連續的、長度介于250位元組至253位元組的節點,當添加一個長度大于等于254位元組的節點時,可能會導緻後續的節點的previous_entry_length屬性從1位元組擴充為5位元組。删除節點也可能産生更新。時間複雜度O(n2)

整數集合是集合鍵的底層實作之一,集合隻包含整數值元素并且元素數量不多。

Redis資料結構

encoding:contents數組中存儲值的類型

int16_t

int32_t

int64_t

length:集合包含的元素個數

contents:按照從小到大的順序儲存元素

更新:當添加一個新元素到整數集合時,并且新元素的類型比整數集合中所有元素類型都要長時,要先進行更新,然後才将新元素添加到整數集合中。

​ 更新整數集合的三步驟:

根據新元素的類型,擴充整數集合底層數組的空間大小,并為新元素配置設定空間

将底層數組現有的所有元素都轉換成與新元素相同的類型,然後将元素放到正确的位置上,需要維持底層數組的有序性

将新元素添加到底層數組

每次更新都需要對底層數組的元素進行類型轉換,是以向整數集合添加新元素的時間複雜度為O(N)

更新後新元素的位置:

​ 引發更新的新元素的長度總是比整數集合所有的元素長度大,

新元素小于所有現有元素時,新元素會被放置在底層數組的最開頭

新元素大于所有現有元素時,新元素會被放置在底層數組的最末尾

更新的好處:提升整數集合的靈活性,盡可能節約記憶體

​ 提升靈活性:整數集合通過自動更新底層數組來适應新元素,是以可以随意将不同類型的整數添加到集合中,靈活性較好。

​ 節約記憶體:整數集合既能儲存三種不同類型的值,又能確定更新操作隻會在有需要的時候進行,可以盡量節省記憶體。

整數集合不支援降級,一旦更新,就會一直保持更新後的狀态

跳表是一種有序的資料結構,通過在每個節點中維持多個指向其他節點的指針,進而達到快速通路節點的目的

跳表是有序集合鍵的底層實作之一,當有序集合中包含的元素個數較多或者元素的成員是比較長的字元串時

redis用到跳表的地方:有序集合鍵、叢集節點的内部資料結構

Redis資料結構

​ 表頭節點的結構:表頭節點也有後退指針、分值和成員對象

header:指向跳躍表的表頭節點

tail:指向跳躍表的表尾節點

level:記錄目前跳躍表内,層數最大的那個節點的層數(表頭節點的層數不計)

length:跳躍表的長度

​ 跳表節點的結構:

level數組:level數組可以包含多個元素,每個元素都包含前進指針和跨度。

​ 前進指針用于從表頭向表尾通路其他節點,跨度則記錄了前進指針所指向節點和目前節點的距離。

​ 層的數量越多,通路其他節點的速度就更快。

​ 每次建立一個新節點時,會随機生成一個1-32之間的值作為level數組的大小。

​ 跨度實際上是用來排位的,而不是周遊:在查找某個節點的過程中,将沿途通路過的所有層的跨度累計得到的結 果就是目标節點在跳表中的排位。

後退指針:指向位于目前節點的前一個節點,可以用于從表尾向表頭周遊時使用,每次隻能後退至前一個節點

分值:在跳表中,節點按各自所儲存的分值從小到大排列

成員對象:節點所儲存的成員對象

各個節點儲存的成員對象必須是唯一的,但是多個節點儲存的分值可以是相同的,如果分值相同,則按照成員對象的字典序來排序,較小的排前面。

Redis資料庫使用字典作為底層實作,字典也是哈希鍵的底層實作之一,當包含的鍵值對比較多或者鍵值對中的元素都是比較長的字元串時

Redis資料結構

字典組成:

type:一個指向dictType結構的指針,每個dictType儲存了一組用于操作特定類型鍵值對的函數

dictType包含的函數:計算hash值;複制鍵;複制值;銷毀鍵;銷毀值;對比鍵

privdata:傳給函數的可選參數

ht:包含兩個項的數組,每個項都是一個哈希表,一般隻是用ht[0],ht[1]隻會在對ht[0]哈希表進行rehash使用

rehashidx:rehash索引,記錄目前rehash的進度,如果目前沒有進行rehash,值為-1

哈希表組成:

table:哈希數組,數組中的每一個元素指向一個entry結構,每個entry儲存一個鍵值對

size:數組的大小

sizemask:等于size - 1,用hash值&sizemask算出鍵應該放在哪個索引上

used:目前哈希表的鍵值對數量

哈希表節點組成:

key:鍵

v:值,可以是一個指針或者uint64_t或者是int64_t

next:指向另一個節點的指針,将多個哈希值相同的鍵值對連接配接起來

雜湊演算法:

使用MurmurHash2算法,調用type中的計算哈希值的函數算出hash,然後和sizemask進行按位&運算

鍵沖突:

采用鍊位址法,為了速度考慮,采用頭插法,總是将新節點添加到連結清單的表頭

rehash:

當儲存的鍵值對太多或太少,需要通過rehash對哈希表的大小進行相應的擴充或者收縮,讓負載因子維持在一個合理範圍内。

rehash步驟:

為哈希表ht[1] 配置設定空間,空間的大小取決于要執行的操作以及ht[0]目前包含的鍵值對數量:

如果是擴充操作,ht[1] 大小為第一個大于等于ht[0].used * 2 的 2n

如果是收縮操作,ht[1] 大小為第一個大于等于ht[0].used 的 2n

将ht[0] 的所有鍵值對rehash到ht[1]

當ht[0] 的所有鍵值對遷移到ht[1]後,釋放ht[0],将ht[1] 設定為ht[0],并為ht[1] 建立一個空白的哈希表,為下一次rehash做準備

哈希表的擴充與收縮:

擴充:

目前沒有在執行 BGSAVE 或 BGREWRITEAOF ,并且負載因子大于等于1

目前有在執行 BGSAVE 或 BGREWRITEAOF,并且負載因子大于等于5

負載因子 = (哈希表已儲存的節點數量)/ 哈希表大小

在執行BGSAVE 或 BGREWRITEAOF 時,需要建立子程序,大多數作業系統都是采用寫時複制來優化子程序使用效率,是以在子程序存在期間,提高執行擴充所需負載因子,避免子程序存在期間進行擴充操作,避免不必要的記憶體寫入,最大限度節約記憶體。

收縮:負載因子小于0.1時,執行收縮操作。

漸進式rehash:

rehash并不是一次性完成,而是分多次、漸進式完成,如果鍵值對數量比較多,要一次性将全部鍵值對rehash到ht[1],可能會導緻伺服器在一段時間内停止服務

為ht[1] 配置設定空間

維持索引計數器rehashidx,并将值設為0,表示rehash開始

rehash期間,每次對字典執行增删改查時,順帶将ht[0] 在rehashidx 索引上的所有鍵值對rehash到ht[1],rehash完成後,rehashidx加1

當ht[0] 全部的鍵值對rehash完成後,将rehashidx設定為-1,表示完成

rehash期間操作:

增:新添加操作隻被儲存到ht[1],ht[0]不再進行任何添加操作,保證ht[0]鍵值對的數量隻增不減,最終變成空表

删、改、查:先在ht[0]裡進行,如果沒有操作成功,再到ht[1]進行。

連結清單是清單鍵的底層實作之一,除此之外,釋出與訂閱、慢查詢、螢幕等功能也用到了連結清單,Redis本身也使用了連結清單儲存多個用戶端的狀态資訊

Redis資料結構

連結清單組成:

head:表頭節點

tail:表尾節點

len:節點的數量

dup:複制連結清單節點所儲存的值

free:釋放連結清單節點所儲存的值

mathch:對比連結清單節點所儲存的值和另一個輸入值是否相等

prev:前驅節點

next:後置節點

value:節點的值

特性:

雙向無環連結清單

帶表頭和表尾指針

帶連結清單長度計數器

多态

Redis中,C字元串隻會作為字元串字面量,用在一些無須對字元串值進行修改的地方,比如列印日志

字元串值的鍵值對都是SDS實作的,SDS還可以被用作緩沖區:AOF緩沖區、用戶端輸入緩沖區

Redis資料結構

len:字元串的長度

free:未使用的位元組數量

buf:位元組數組,用于儲存字元串

SDS遵循C字元串以空字元結尾的慣例,空字元的1位元組空間不計算在len中,好處:可以直接重用一部分C字元串函數。

SDS與C字元串的差別:

常數複雜度擷取字元串長度

C字元串不記錄自身的長度,要擷取長度,必須周遊,O(N);SDS隻要反問len屬性就可以知道長度,即使對一個非常長的字元串鍵反複執行擷取長度的指令,也不會對系統造成任何影響。

杜絕緩沖區溢出

C字元串如果沒有配置設定足夠的記憶體空間,在執行修改字元串時可能會溢出。SDS杜絕了緩沖區溢出的可能性:當SDS修改時,會先檢查SDS空間是否滿足修改所需的要求,不滿足自動将SDS擴充至執行修改所需的大小,然後執行實際修改操作。

減少修改字元串時的記憶體重配置設定次數

每次增長或縮短一個C字元串,都要進行一次記憶體重配置設定操作:

如果是增長操作,需要先通過記憶體重配置設定來擴充底層數組,忘了這一步會産生緩沖區溢出

如果是縮短操作,需要先通過記憶體重配置設定來釋放不再使用的空間,忘 了這一步會産生記憶體洩漏

記憶體重配置設定是一個耗時的操作,如果每次修改字元串都要進行記憶體重配置設定,在修改頻繁的情況下,會對性能造成影響。

SDS的空間預配置設定和惰性空間兩種優化政策:

空間預配置設定:每次修改并需要對SDS進行擴充時,除了配置設定修改所需要的空間,還需要配置設定額外未使用的空間

如果修改之後,SDS的長度小于1MB,将配置設定和len屬性同樣大小的未使用空間,這時buf實際長度等于len位元組+free位元組+1位元組

如果修改之後,SDS的長度大于等于1MB,将配置設定1MB未使用空間,buf實際長度等于lenMB+1MB+1位元組

通過空間預配置設定,可以減少連續執行字元串增長操作所需的記憶體重配置設定次數,和C字元串對比,将連續增長N次所需的記憶體重配置設定次數從必定的N次降低為最多N次

惰性空間釋放:優化縮短SDS操作,需要縮短SDS時,不是立即用記憶體重配置設定來回收縮短後多出來的位元組,而是使用free記錄這些位元組的數量,并等待将來使用。

通過惰性空間釋放,SDS避免了縮短字元串所需的記憶體重配置設定操作,并為将來可能有的增長操作提供了優化

二進制安全

C字元串除了字元串末尾的空字元外,裡面不能包含空字元,使得隻能儲存文本資料,不能儲存圖檔、音頻、視訊等二進制檔案。

SDS的buf屬性是儲存二進制資料,SDS會以處理二進制的方式處理buf數組,資料寫入時什麼樣,讀出時就什麼樣。

重用部分C字元串函數

SDS遵循C字元串以空字元結尾,可以讓儲存文本資料的SDS重用一部分C字元串函數。

繼續閱讀