1. 程式人生 > 實用技巧 >Redis資料結構原理

Redis資料結構原理

可能就前面的記錄一下,後面的很多是截圖存在Typora上。。粘過來不顯示,我也懶得弄了,應該是比較完整的筆記記錄了,這裡只放了一小部分,有了這些完全可以自己寫一個小Redis玩具了,有空試試吧

簡單動態字串SDS

Redis沒有直接使用C語言傳統的字串表示,而使自己構建了一種名為簡單動態字串的抽象SDS

C 字串SDS
獲取字串長度的複雜度為 O(N) 。 獲取字串長度的複雜度為 O(1) 。
API 是不安全的,可能會造成緩衝區溢位。 API 是安全的,不會造成緩衝區溢位。
修改字串長度 N 次必然需要執行 N 次記憶體重分配。 修改字串長度 N 次最多需要執行 N 次記憶體重分配。
只能儲存文字資料。 可以儲存文字或者二進位制資料。
可以使用所有 庫中的函式。 | 可以使用一部分 庫中的函式。

Redis 的連結串列實現的特性可以總結如下:

  • 雙端: 連結串列節點帶有 prevnext 指標, 獲取某個節點的前置節點和後置節點的複雜度都是 O(1) 。

  • 無環: 表頭節點的 prev 指標和表尾節點的 next 指標都指向 NULL , 對連結串列的訪問以 NULL 為終點。

  • 帶表頭指標和表尾指標: 通過 list 結構的 head 指標和 tail 指標, 程式獲取連結串列的表頭節點和表尾節點的複雜度為 O(1) 。

  • 帶連結串列長度計數器: 程式使用 list

    結構的 len 屬性來對 list 持有的連結串列節點進行計數, 程式獲取連結串列中節點數量的複雜度為 O(1) 。

  • 多型: 連結串列節點使用 void* 指標來儲存節點值, 並且可以通過 list 結構的 dupfreematch 三個屬性為節點值設定型別特定函式, 所以連結串列可以用於儲存各種不同型別的值。

字典dict

rehash

一般字典都會有ht[0] ht[1]兩個字典,而ht1就是rehash時用的

步驟:

  1. 判斷是收縮還是擴容,確定ht1的size

  2. 將ht0的資料遷移到ht1

  3. 釋放ht0,ht1成為ht0,新建ht1

當滿足以下條件時,雜湊表會進行擴充套件和收縮

  1. 伺服器目前沒有執行BGSIVE或者BGREWRITEAOF命令,並且雜湊表的負載因子>=1

  2. 伺服器目前正在執行這兩條命令之一,且負載因子>=5

  3. 負載因子=ht[0].used / ht[0].size

  4. 負載因子小於0.1 進行收縮

原因:處於對執行BGSIVE或者BGREWRITEAOF命令時期時,redis會建立伺服器程序的子程序,一般採用寫時複製的策略優化子程序效率,所以子程序存在時會盡量避免rehash,因此提高了此時所需的負載因子

其次:rehash是漸進rehash

也就是說redis實際執行rehash時是採用每次對字典增刪改查時順便rehash,一些操作過後,字典rehash完成。當然了,在此期間,比如對字典的查詢是在ht0和ht1上都進行查詢

跳躍表

跳躍表是一種有序資料結構,支援Ologn的查詢,最壞On。還可以通過順序性操作批量處理節點

  • header | tail 頭尾指標

  • level 跳躍表層數最大的節點(排除表頭節點)

  • 每層 記錄了這一跳的前進指標以及跨度這兩個屬性,顯然層數越多跳躍表的查詢遍歷就越快,跨度用來統計走過的距離,方便統計Rank

  • 後退製作backward BW標記了向前一個結點的倒退指標,以實現表尾到表頭的遍歷操作

  • 分值和物件,value從小到大構成跳躍表,物件儲存一個SDS,value相同時按SDS字典序排

遍歷時從高層往下查,碰到的前進指標跨度是1是往下走,知道碰到null證明到頭了

整數集合

整數集合是集合間的底層實現之一,當set只有整數元素並且數量不多時,會採用整數集合

升級,但不支援降級

每當新新增的整數型別比現有型別長時,會進行升級操作,這樣的動態策略可以節省記憶體

壓縮列表

列表鍵和雜湊鍵的底層實現之一

列表短且只有整數時使用壓縮列表,zset的鍵值對數量小於128,長度都小於64位元組時也使用這個

AOF

與rdb儲存資料庫鍵值對不同,AOF持久化通過儲存Redis執行的寫命令持久化

AOF持久化的實現

可以分為命令 追加append 檔案寫入 檔案同步sync三個步驟

命令追加

比如SET KEY VALUE執行後,這條命令會最佳到伺服器狀態的aof_buf

緩衝區的末尾

AOF檔案的寫入與同步以及appendfsync選項

Redis 伺服器程序就是一個時間迴圈,這個迴圈中的檔案事件負責接收客戶端的命令請求,以及向客戶端傳送命令回覆,而時間事件負責執行serverCron函式這樣需要定時執行的函式,

fsync和fdatasync同步函式

現代作業系統中使用者呼叫write函式,通常先把資料放到記憶體緩衝區中,等到緩衝區滿了或到了指定時間才會寫入,這樣雖然提高效率但也有停機時的不安全性,使用這兩個同步函式強制作業系統將緩衝區的資料寫入磁碟

appendfsync選項的值得效果

always時,最安全但也最慢,他最多隻丟失一個事件迴圈中的命令資料

everysec時,每秒都有負責這個的子執行緒進行同步,夠快且最多丟失一秒的追加命令

no時,同樣每個事件迴圈都把緩衝區資料寫到aof,但aof檔案的同步由作業系統決定,因為一般不需要同步,所以這種模式的aof檔案寫入速度很快,但一旦發生丟失資料則會丟失上次同步aof後的所有資料

AOF重寫

利用命令的合併和覆蓋,建立一個新的aof檔案替代原有的aof,新的aof只執行持久化必須的一部分的命令體積小很多

重寫虛擬碼p150

子程序AOF重寫後的同步