日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

01丨数据结构:快速的Redis有哪些慢操作

發(fā)布時(shí)間:2024/10/6 数据库 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 01丨数据结构:快速的Redis有哪些慢操作 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Redis數(shù)據(jù)類(lèi)型與底層數(shù)據(jù)類(lèi)型關(guān)系

簡(jiǎn)單來(lái)說(shuō),底層數(shù)據(jù)結(jié)構(gòu)一共有 6 種,分別是簡(jiǎn)單動(dòng)態(tài)字符串、雙向鏈表、壓縮列表、哈希表、跳表和整數(shù)數(shù)組。它們和數(shù)據(jù)類(lèi)型的對(duì)應(yīng)關(guān)系如下圖所示:

鍵和值的結(jié)構(gòu)組織

為了實(shí)現(xiàn)從鍵到值的快速訪問(wèn),Redis 使用了一個(gè)哈希表來(lái)保存所有鍵值對(duì)。哈希桶中的元素保存的并不是值本身,而是指向具體值的指針。這也就是說(shuō),不管值是 String,還是集合類(lèi)型,哈希桶中的元素都是指向它們的指針。
在下圖中,可以看到,哈希桶中的 entry 元素中保存了key和value指針,分別指向了實(shí)際的鍵和值,這樣一來(lái),即使值是一個(gè)集合,也可以通過(guò)*value指針被查找到。

因?yàn)檫@個(gè)哈希表保存了所有的鍵值對(duì),所以,我也把它稱為全局哈希表。哈希表的最大好處很明顯,就是讓我們可以用 O(1) 的時(shí)間復(fù)雜度來(lái)快速查找到鍵值對(duì)——我們只需要計(jì)算
鍵的哈希值,就可以知道它所對(duì)應(yīng)的哈希桶位置,然后就可以訪問(wèn)相應(yīng)的 entry 元素。
你看,這個(gè)查找過(guò)程主要依賴于哈希計(jì)算,和數(shù)據(jù)量的多少并沒(méi)有直接關(guān)系。也就是說(shuō),不管哈希表里有 10 萬(wàn)個(gè)鍵還是 100 萬(wàn)個(gè)鍵,我們只需要一次計(jì)算就能找到相應(yīng)的鍵。
但是,如果你只是了解了哈希表的 O(1) 復(fù)雜度和快速查找特性,那么,當(dāng)你往 Redis 中寫(xiě)入大量數(shù)據(jù)后,就可能發(fā)現(xiàn)操作有時(shí)候會(huì)突然變慢了。這其實(shí)是因?yàn)槟愫雎粤艘粋€(gè)潛在的風(fēng)險(xiǎn)點(diǎn),那就是哈希表的沖突問(wèn)題和 rehash 可能帶來(lái)的操作阻塞。

為什么哈希表操作變慢了?

當(dāng)你往哈希表中寫(xiě)入更多數(shù)據(jù)時(shí),哈希沖突是不可避免的問(wèn)題。這里的哈希沖突,也就是指,兩個(gè) key 的哈希值和哈希桶計(jì)算對(duì)應(yīng)關(guān)系時(shí),正好落在了同一個(gè)哈希桶中。
畢竟,哈希桶的個(gè)數(shù)通常要少于 key 的數(shù)量,這也就是說(shuō)難免會(huì)有一些 key 的哈希值對(duì)應(yīng)到了同一個(gè)哈希桶中。
Redis 解決哈希沖突的方式,就是鏈?zhǔn)焦?。鏈?zhǔn)焦R埠苋菀桌斫?#xff0c;就是指同一個(gè)哈希桶中的多個(gè)元素用一個(gè)鏈表來(lái)保存,它們之間依次用指針連接。
如下圖所示:entry1、entry2 和 entry3 都需要保存在哈希桶 3 中,導(dǎo)致了哈希沖突。此時(shí),entry1 元素會(huì)通過(guò)一個(gè)next指針指向 entry2,同樣,entry2 也會(huì)通過(guò)next指針指向 entry3。這樣一來(lái),即使哈希桶 3 中的元素有 100 個(gè),我們也可以通過(guò) entry 元素中的指針,把它們連起來(lái)。這就形成了一個(gè)鏈表,也叫作哈希沖突鏈。

但是,這里依然存在一個(gè)問(wèn)題,哈希沖突鏈上的元素只能通過(guò)指針逐一查找再操作。如果 哈希表里寫(xiě)入的數(shù)據(jù)越來(lái)越多,哈希沖突可能也會(huì)越來(lái)越多,這就會(huì)導(dǎo)致某些哈希沖突鏈 過(guò)長(zhǎng),進(jìn)而導(dǎo)致這個(gè)鏈上的元素查找耗時(shí)長(zhǎng),效率降低。對(duì)于追求“快”的 Redis 來(lái)說(shuō), 這是不太能接受的。
所以,Redis 會(huì)對(duì)哈希表做 rehash 操作。rehash 也就是增加現(xiàn)有的哈希桶數(shù)量,讓逐漸增多的 entry 元素能在更多的桶之間分散保存,減少單個(gè)桶中的元素?cái)?shù)量,從而減少單個(gè)桶中的沖突。那具體怎么做呢?
其實(shí),為了使 rehash 操作更高效,Redis 默認(rèn)使用了兩個(gè)全局哈希表:哈希表 1 和哈希表 2。一開(kāi)始,當(dāng)你剛插入數(shù)據(jù)時(shí),默認(rèn)使用哈希表 1,此時(shí)的哈希表 2 并沒(méi)有被分配空間。隨著數(shù)據(jù)逐步增多,Redis 開(kāi)始執(zhí)行 rehash,這個(gè)過(guò)程分為三步:

  • 給哈希表 2 分配更大的空間,例如是當(dāng)前哈希表 1 大小的兩倍;
  • 把哈希表 1 中的數(shù)據(jù)重新映射并拷貝到哈希表 2 中;
  • 釋放哈希表 1 的空間。

到此,我們就可以從哈希表 1 切換到哈希表 2,用增大的哈希表 2 保存更多數(shù)據(jù),而原來(lái)的哈希表 1 留作下一次 rehash 擴(kuò)容備用。
這個(gè)過(guò)程看似簡(jiǎn)單,但是第二步涉及大量的數(shù)據(jù)拷貝,如果一次性把哈希表 1 中的數(shù)據(jù)都遷移完,會(huì)造成 Redis 線程阻塞,無(wú)法服務(wù)其他請(qǐng)求。此時(shí),Redis 就無(wú)法快速訪問(wèn)數(shù)據(jù)了。為了避免這個(gè)問(wèn)題,Redis 采用了漸進(jìn)式 rehash。
簡(jiǎn)單來(lái)說(shuō)就是在第二步拷貝數(shù)據(jù)時(shí),Redis 仍然正常處理客戶端請(qǐng)求,每處理一個(gè)請(qǐng)求時(shí),從哈希表 1 中的第一個(gè)索引位置開(kāi)始,順帶著將這個(gè)索引位置上的所有 entries 拷貝到哈希表 2 中;等處理下一個(gè)請(qǐng)求時(shí),再順帶拷貝哈希表 1 中的下一個(gè)索引位置的entries。如下圖所示:

有哪些底層數(shù)據(jù)結(jié)構(gòu)?

集合類(lèi)型的底層數(shù)據(jù)結(jié)構(gòu)主要有 5 種:整數(shù)數(shù)組、雙向表、哈希表、壓縮列表和跳表。
其中,哈希表的操作特點(diǎn)我們剛剛已經(jīng)學(xué)過(guò)了;整數(shù)數(shù)組和雙向鏈表也很常見(jiàn),它們的操作特征都是順序讀寫(xiě),也就是通過(guò)數(shù)組下標(biāo)或者鏈表的指針逐個(gè)元素訪問(wèn),操作復(fù)雜度基本是 O(N),操作效率比較低;壓縮列表和跳表我們平時(shí)接觸得可能不多,但它們也是Redis 重要的數(shù)據(jù)結(jié)構(gòu),所以我來(lái)重點(diǎn)解釋一下。
壓縮列表實(shí)際上類(lèi)似于一個(gè)數(shù)組,數(shù)組中的每一個(gè)元素都應(yīng)保存一個(gè)數(shù)據(jù)。和數(shù)組不同的是,壓縮列表在表頭有三個(gè)字段 zlbytes、zltail 和 zllen,分別表示列表長(zhǎng)度、列表尾的偏移量和列表中的 entry 個(gè)數(shù);壓縮列表在表尾還有一個(gè) zlend,表示列表結(jié)束。

在壓縮列表中,如果我們要查找定位第一個(gè)元素和最后一個(gè)元素,可以通過(guò)表頭三個(gè)字段的長(zhǎng)度直接定位,復(fù)雜度是 O(1)。而查找其他元素時(shí),就沒(méi)有這么高效了,只能逐個(gè)查找,此時(shí)的復(fù)雜度就是 O(N) 了。
我們?cè)賮?lái)看下跳表,有序鏈表只能逐一查找元素,導(dǎo)致操作起來(lái)非常緩慢,于是就出現(xiàn)了跳表。具體來(lái)說(shuō),跳表在鏈表的基礎(chǔ)上,增加了多級(jí)索引,通過(guò)索引位置的幾個(gè)跳轉(zhuǎn),實(shí)現(xiàn)數(shù)據(jù)的快速定位,如下圖所示:

如果我們要在鏈表中查找 33 這個(gè)元素,只能從頭開(kāi)始遍歷鏈表,查找 6 次,直到找到 33為止。此時(shí),復(fù)雜度是 O(N),查找效率很低。
為了提高查找速度,我們來(lái)增加一級(jí)索引:從第一個(gè)元素開(kāi)始,每?jī)蓚€(gè)元素選一個(gè)出來(lái)作為索引。這些索引再通過(guò)指針指向原始的鏈表。例如,從前兩個(gè)元素中抽取元素 1 作為一級(jí)索引,從第三、四個(gè)元素中抽取元素 11 作為一級(jí)索引。此時(shí),我們只需要 4 次查找就能定位到元素 33 了。
如果我們還想再快,可以再增加二級(jí)索引:從一級(jí)索引中再抽取部分元素作為二級(jí)索引。例如,從一級(jí)索引中抽取 1、27、100 作為二級(jí)索引,二級(jí)索引指向一級(jí)索引。這樣,我們只需要 3 次查找,就能定位到元素 33 了??梢钥吹?#xff0c;這個(gè)查找過(guò)程就是在多級(jí)索引上跳來(lái)跳去,最后定位到元素。這也正好符合“跳”表的叫法。
當(dāng)數(shù)據(jù)量很大時(shí),跳表的查找復(fù)雜度就是 O(logN)。
好了,我們現(xiàn)在可以按照查找的時(shí)間復(fù)雜度給這些數(shù)據(jù)結(jié)構(gòu)分下類(lèi)了:

不同操作的復(fù)雜度

我總結(jié)了一個(gè)“四句口訣”,希望能幫助你快速記住集合常見(jiàn)操作的復(fù)雜度。這樣你在使用過(guò)程中,就可以提前規(guī)避高復(fù)雜度操作了。

  • 單元素操作是基礎(chǔ);
  • 范圍操作非常耗時(shí);
  • 統(tǒng)計(jì)操作通常高效;
  • 例外情況只有幾個(gè)。

第一,單元素操作,是指每一種集合類(lèi)型對(duì)單個(gè)數(shù)據(jù)實(shí)現(xiàn)的增刪改查操作。例如,Hash 類(lèi)型的 HGET、HSET 和HDEL,Set 類(lèi)型的 SADD、SREM、SRANDMEMBER 等。這些作的復(fù)雜度由集合采用的數(shù)據(jù)結(jié)構(gòu)決定,例如HGET、
HSET 和 HDEL 是對(duì)哈希表做操作,所以它們的復(fù)雜度都是 O(1);Set 類(lèi)型用哈希表作為底層數(shù)據(jù)結(jié)構(gòu)時(shí),它的SADD、SREM、SRANDMEMBER 復(fù)雜度也是 O(1)。
這里,有個(gè)地方你需要注意一下,集合類(lèi)型支持同時(shí)對(duì)多個(gè)元素進(jìn)行增刪改查,例如 Hash類(lèi)型的 HMGET 和 HMSET,Set 類(lèi)型的 SADD 也支持同時(shí)增加多個(gè)元素。此時(shí),這些操作的復(fù)雜度,就是由單個(gè)元素操作復(fù)雜度和元素個(gè)數(shù)決定的。例如,HMSET 增加 M 個(gè)元素時(shí),復(fù)雜度就從 O(1) 變成 O(M) 了。
第二,范圍操作,是指集合類(lèi)型中的遍歷操作,可以返回集合中的所有數(shù)據(jù),比如 Hash類(lèi)型的 HGETALL 和 Set 類(lèi)型的 SMEMBERS,或者返回一個(gè)范圍內(nèi)的部分?jǐn)?shù)據(jù),比如 List類(lèi)型的 LRANGE 和 ZSet 類(lèi)型的 ZRANGE。這類(lèi)操作的復(fù)雜度一般是 O(N),比較耗時(shí),我們應(yīng)該盡量避免。
不過(guò),Redis 從 2.8 版本開(kāi)始提供了 SCAN 系列操作(包括 HSCAN,SSCAN 和ZSCAN),這類(lèi)操作實(shí)現(xiàn)了漸進(jìn)式遍歷,每次只返回有限數(shù)量的數(shù)據(jù)。這樣一來(lái),相比于
HGETALL、SMEMBERS 這類(lèi)操作來(lái)說(shuō),就避免了一次性返回所有元素而導(dǎo)致的 Redis 阻塞。
第三,統(tǒng)計(jì)操作,是指集合類(lèi)型對(duì)集合中所有元素個(gè)數(shù)的記錄,例如 LLEN 和 SCARD。這類(lèi)操作復(fù)雜度只有 O(1),這是因?yàn)楫?dāng)集合類(lèi)型采用壓縮列表、雙向鏈表、整數(shù)數(shù)組這些數(shù)據(jù)結(jié)構(gòu)時(shí),這些結(jié)構(gòu)中專門(mén)記錄了元素的個(gè)數(shù)統(tǒng)計(jì),因此可以高效地完成相關(guān)操作。
第四,例外情況,是指某些數(shù)據(jù)結(jié)構(gòu)的特殊記錄,例如壓縮列表和雙向鏈表都會(huì)記錄表頭和表尾的偏移量。這樣一來(lái),對(duì)于 List 類(lèi)型的 LPOP、RPOP、LPUSH、RPUSH 這四個(gè)操作來(lái)說(shuō),它們是在列表的頭尾增刪元素,這就可以通過(guò)偏移量直接定位,所以它們的復(fù)雜度也只有 O(1),可以實(shí)現(xiàn)快速操作。

總結(jié)

以上是生活随笔為你收集整理的01丨数据结构:快速的Redis有哪些慢操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。