《Redis核心技术与实战》学习总结(1)
【Redis】| 總結/Edison Zhou
0寫在開頭
作為Key/Value鍵值數據庫,Redis的應用非常廣泛。在之前多年的工作生涯中,我也只是關注了零散的技術點,沒有對Redis建立起一套整體觀,但只有建立了系統整體觀,才能更好地定位問題和解決問題,更重要的是應付面試。
剛好,極客時間推出了一門《Redis核心技術與實戰》課程,于是我就入手了,這是我的學習總結,化繁為簡,只留重點,分享與你。
1KV數據庫的基本架構
為了建立系統觀,最好的方法就是對其總體架構和關鍵模塊有一個全局認知,因此這門課在最開始便給出了一個簡單的KV數據庫的基本架構,它包括了:
(1)訪問框架
(2)索引模塊
(3)操作模塊
(4)存儲模塊
SimpleKV -?來自極客時間
為了支持更加豐富的業務場景,其實 Redis 對這些組件或者功能進行了擴展,或者說是進行了精細優化,從而滿足了功能和性能等方面的要求。
下圖展示了從SimpleKV到Redis的變化:
SimpleKV to Redis -?來自極客時間
上圖中展示了幾點變化:
一是Redis主要使用網絡框架來進行訪問,使得Redis可以作為一個獨立的基礎網絡服務;
二是Redis數據模型中的value類型更加豐富,可以提供更多的接口;
三是Redis擴展了持久化模塊,提供日志(AOF)和快照(RDB)。
四是Redis提供了集群的支持,使得HA和可擴展成為可能。
2Redis的數據結構
Redis之所以“快”,一方面因為它是內存數據庫,所有操作都在內存上完成,內存的訪問速度本來就快。另一方面則是因為高效的數據結構,使得操作鍵值效率較高。
總體來說,Redis使用了一個用來保存每個Key/Value的全局哈希表結構,其中Value類型又包括了支持集合類型的雙向鏈表、壓縮列表、跳表等五大底層結構。
K/V的組織結構:全局哈希表
Redis使用了一個全局維度的哈希表來保存所有的Key/Value,每個哈希表本質上都是一個數組,這個數組的每個元素稱為一個哈希桶。
哈希桶中的元素保存的并不是Value本身,而是指向Value的指針,如下圖所示:
由數據結構的知識可以知道,哈希表的時間復雜度為O(1),因此它非常適合快速查找的場景。
哈希沖突及其解決
當往哈希表中寫入的數據變的很多時,哈希沖突問題就會出現。所謂哈希沖突,就是兩個Key的哈希值正好落到了同一個哈希桶中(哈希桶的個數畢竟通常少于Key的個數)。
因此,和我們在數據結構課程學到的一致,Redis也采用了鏈式哈希來解決哈希沖突。所謂鏈式哈希,就是同一個哈希桶中的多個元素用一個鏈表來保存,它們之間依次用指針連接,如下圖所示:
但是,如果哈希表里寫入的數據越來越多,哈希沖突鏈也會進而變得很長,從而導致這個鏈條上得元素查找耗時長,效率降低。
因此,Redis還會對哈希表做rehash操作。所謂rehash,就是增加現有的哈希桶的數量,讓逐漸增多的entry元素能夠在更多的桶之間分散保存,減少單個桶中的元素數量,從而減少單個桶中的沖突。
在具體操作中,Redis會開辟一個新的哈希表(比如:大小為之前的兩倍),然后把之前哈希表的數據重新映射到新的哈希表,最后釋放之前的哈希表。
畫外音:是不是跟.NET中的集合類型動態擴容的思想類似?
But,在拷貝之前哈希表數據到新哈希表時,涉及到數據量過大,有可能會造成Redis的線程阻塞,從而無法服務其他的請求。因此,Redis采用了漸進式哈希的解決方案。
簡單來說,所謂漸進式哈希就是不一次性把老哈希表中的數據遷移完,而是在每次處理一個請求時,從老哈希表中的第一個索引位置開始,順帶著將這個索引位置上的所有entries拷貝到新哈希表中;等下一個請求時,再順帶拷貝下一個索引位置的entries。如此,便將一次性的大量拷貝的開銷,分攤到多次處理請求的過程中,避免了耗時的操作 和 服務的中斷。
漸進式哈希的示例流程如下圖所示:
此外,漸進式rehash執行時,除了根據鍵值對的操作來進行數據遷移,Redis本身還會有一個定時任務在執行rehash,如果沒有鍵值對操作時,這個定時任務會周期性地(例如每100ms一次)搬移一些數據到新的哈希表中,這樣可以縮短整個rehash的過程。
?底層數據結構概覽
Redis的Key/Value是通過哈希表進行組織的,對于String類型,找到哈希桶就可以直接CRUD了,因此O(1)就是String類型的操作復雜度了。
但是,對于集合類型來說,還需要進一步操作。在此之前,我們先了解一下都有哪些底層數據結構。
我們所熟知的String、List、Hash、Sorted Set 和 Set其實只是Redis中數據的保存形式,而一般所說的數據結構其實是它們的底層實現。
如下圖所示,Redis的底層數據結構一共有6種,其和常見數據類型的對應關系已經清晰地體現在了下面這張圖片里(給個贊)。
Redis數據類型與底層數據結構 -?來自極客時間
除了String類型的底層實現只有一種,即簡單動態字符串。其余數據類型(List、Hash、Sorted Set和Set)都有兩種底層實現,他們又被稱為集合類型,即一個鍵對應了一個集合的數據。
底層數據結構復雜度
通過上面的描述,我們可以知道哈希表的復雜度是O(1),而數組 和 雙向鏈表 也很常見,其復雜度基本是O(N)。學習過數據結構課程的都知道,O(N)的操作效率并不高,因此Redis引入了壓縮列表 和 跳表。雖然壓縮列表和跳表并不為我們所熟知,但它倆卻是對整數數組和雙向鏈表所做的優化。
首先,來看壓縮列表,壓縮列表也類似于一個數組,但和數組不同的是,壓縮列表在表頭有三個字段,分別表示列表長度、列表尾的偏移量 和 列表中entry的個數。壓縮列表在表尾還有一個字段,表示列表結束,如下圖所示:
由上圖可知,在壓縮列表中定位第一個和最后一個元素,只需要通過表頭三個字段即可直接定位,無須遍歷一次,因此查找首元素和尾元素的復雜度為O(1)。而需要查找其他元素時,就只能遍歷查找了,此情景的復雜度退化為O(N)。
然后,來看跳表。跳表是在鏈表的基礎之上,增加了多級索引,通過索引位置的幾個跳轉,實現數據的快速定位。一個具體的查找案例,如下圖所示:
從上圖可以看到,這個查找過程其實是在多級索引上跳來跳去,最后定位到具體的要查找的元素。我們常聽到,沒有什么不是一頓火鍋解決不了的事情,如果不行,那就兩頓。對于跳表來說,沒有什么不是增加索引解決不了的查詢,如果不夠快,那就再加一級索引。
當數據量非常大時,跳表的查找復雜度就是O(logN),學過數據結構的都知道,它比O(N)效率要高。因此,我們也可以知道Sorted Set類型在數據元素較少時采用壓縮列表,而一旦超過閾值,就會轉為跳表結構來保證查詢效率不受影響。
End總結
本文總結了Redis的基礎架構 和 底層數據結構,這部分內容通常也是后端開發面試中的常見內容,希望對你有所幫助。
參考資料
極客時間,蔣德鈞《Redis核心技術與實戰》
年終總結:Edison的2020年終總結
數字化轉型:我在傳統企業做數字化轉型
C#刷題:C#刷劍指Offer算法題系列文章目錄
.NET面試:.NET開發面試知識體系
.NET大會:2020年中國.NET開發者大會PDF資料
??
?
總結
以上是生活随笔為你收集整理的《Redis核心技术与实战》学习总结(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软认真聆听了开源 .NET 开发社区的
- 下一篇: 如何捕获 EF 生成的 SQL 脚本?