Redis radix tree原始碼解析

java架構codi發表於2019-04-19

Redis實現了不定長壓縮字首的radix tree,用在叢集模式下儲存slot對應的的所有key資訊。本文將詳述在Redis中如何實現radix tree。

核心資料結構

raxNode是radix tree的核心資料結構,其結構體如下程式碼所示:

typedef struct raxNode {
    uint32_t iskey:1;     
    uint32_t isnull:1;    
    uint32_t iscompr:1;   
    uint32_t size:29;     
    unsigned char data[];
} raxNode;
複製程式碼

  • iskey:表示這個節點是否包含key

    • 0:沒有key
    • 1:表示從頭部到其父節點的路徑完整的儲存了key,查詢的時候按子節點iskey=1來判斷key是否存在
  • isnull:是否有儲存value值,比如儲存後設資料就只有key,沒有value值。value值也是儲存在data中
  • iscompr:是否有字首壓縮,決定了data儲存的資料結構
  • size:該節點儲存的字元個數
  • data:儲存子節點的資訊

    • iscompr=0:非壓縮模式下,資料格式是:[header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?),有size個字元,緊跟著是size個指標,指向每個字元對應的下一個節點。size個字元之間互相沒有路徑聯絡。
    • iscompr=1:壓縮模式下,資料格式是:[header strlen=3][xyz][z-ptr](value-ptr?),只有一個指標,指向下一個節點。size個字元是壓縮字元片段

Rax Insert

以下用幾個示例來詳解rax tree插入的流程。假設j是遍歷已有節點的遊標,i是遍歷新增節點的遊標。

場景一:只插入abcd

z-ptr指向的葉子節點iskey=1,使用了壓縮字首。

場景二:在abcd之後插入abcdef

從abcd父節點的每個壓縮字首字元比較,遍歷完所有abcd節點後指向了其空子節點,j = 0, i < len(abcded)。
查詢到abcd的空子節點,直接將ef賦值到子節點上,成為abcd的子節點。ef節點被標記為iskey=1,用來標識abcd這個key。ef節點下再建立一個空子節點,iskey=1來表示abcdef這個key。

場景三:在abcd之後插入ab

ab在abcd能找到前兩位的字首,也就是i=len(ab),j < len(abcd)。
將abcd分割成ab和cd兩個子節點,cd也是一個壓縮字首節點,cd同時被標記為iskey=1,來表示ab這個key。
cd下掛著一個空子節點,來標記abcd這個key。

場景四:在abcd之後插入abABC

abcABC在abcd中只找到了ab這個字首,即i < len(abcABC),j < len(abcd)。這個步驟有點複雜,分解一下:

  • step 1:將abcd從ab之後拆分,拆分成ab、c、d 三個節點。
  • step 2:c節點是一個非壓縮的節點,c掛在ab子節點上。
  • step 3:d節點只有一個字元,所以也是一個非壓縮節點,掛在c子節點上。
  • step 4:將ABC 拆分成了A和BC, A掛在ab子節點上,和c節點屬於同一個節點,這樣A就和c同屬於父節點ab。
  • step 5:將BC作為一個壓縮字首的節點,掛在A子節點下。
  • step 6:d節點和BC節點都掛一個空子節點分別標識abcd和abcABC這兩個key。

場景五:在abcd之後插入Aabc

abcd和Aabc沒有字首匹配,i = 0,j = 0。
將abcd拆分成a、bcd兩個節點,a節點是一個非壓縮字首節點。
將Aabc拆分成A、abc兩個節點,A節點也是一個非壓縮字首節點。
將A節點掛在和a相同的父節點上。
同上,在bcd和abc這兩個節點下掛空子節點來分別表示兩個key。

Rax Remove

刪除

刪除一個key的流程比較簡單,找到iskey的節點後,向上遍歷父節點刪除非iskey的節點。如果是非壓縮的父節點並且size > 1,表示還有其他非相關的路徑存在,則需要按刪除子節點的模式去處理這個父節點,主要是做memove和realloc。

合併

刪除一個key之後需要嘗試做一些合併,以收斂樹的高度。
合併的條件是:

  • iskey=1的節點不能合併
  • 子節點只有一個字元
  • 父節點只有一個子節點(如果父節點是壓縮字首的節點,那麼只有一個子節點,滿足條件。如果父節點是非壓縮字首的節點,那麼只能有一個字元路徑才能滿足條件)


相關文章