1. 程式人生 > 其它 >redis session java獲取attribute_redis裡的資料結構

redis session java獲取attribute_redis裡的資料結構

技術標籤:redis session java獲取attribute

Redis作為當前使用非常廣泛的記憶體資料庫,在程式碼層面做了很多極致的優化,已獲取更好的效能。其中重要的一部分,就是對於底層資料結構的使用。Redis會根據資料量、資料大小等來優化對於不同結構的使用,從而獲得更佳的執行效率和記憶體佔用。Redis的核心資料結構包括簡單動態字串、列表、字典、跳躍表、整數集合、壓縮列表。

接下來,我們就依次講講這些資料結構。

簡單動態字串(SDS)

Redis是用C語言實現的。先複習一下C,C裡的字串中不記錄字串長度,以空字元標記結尾。這樣會顯而易見的帶來三個問題:1.獲取字串長度需要O(n)的複雜度;2.操作不慎會導致緩衝區溢位,例如記憶體中緊鄰的兩個字串,如果對前一個呼叫strcat拼接其他字串,就會造成溢位;3. 一些特殊內容,如影象、音訊等轉成二進位制時,難免其中夾雜空字元等特殊字元,這樣就無法被C字串儲存了,即C字串不具備二進位制安全性。

而這幾點,對於Redis的應用場景來說,影響其實都是非常大的。因此,在redis中定義了一個新的結構,用來儲存字串,即SDS。

SDS的核心思想就是額外使用一個欄位記錄字串的長度,這樣,上面三個問題就都迎刃而解了。

此外,redis從4.0開始對SDS做了一個程式碼層面的優化,優化了記憶體佔用,不過不影響其底層邏輯。

這是redis 3.0裡SDS的原始碼:

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

而這是redis 4.0之後SDS的原始碼:

  struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

可以看到,在新版的原始碼裡,資料儲存會根據情況使用uint8,uint16等不同型別。在C裡,一個int佔用4個位元組,因此,對於原版的SDS來說,即使儲存的資訊非常少,也會固定佔到8個位元組。而uint8只佔一個位元組,uint16只佔2個位元組,對於小資料來說,redis的記憶體佔用會有明顯優化。

此外,redis會有空間預分配、惰性釋放等機制,減少記憶體分配的次數。SDS的實現方式也保證了大部分方法可以相容C字串,減少了大量實現成本。

連結串列

Redis裡的連結串列是一個普通的雙向無環連結串列,相信大家都很熟悉了,就不細說了,結構如下。

typedef struct listNode {

    struct listNode *prev;

    struct listNode *next;

    void *value;

} listNode;

Redis中的列表物件,底層就是連結串列。

字典

字典也就是我們常說的map。

typedef struct dictht {

    dictEntry **table;

    unsigned long size; //hash表長度

    unsigned long sizemask;

    unsigned long used; //已有的長度

} dictht;

Redis中的字典是hash表,使用鏈地址法解決hash地址衝突。

類似於java等語言中的hashMap, redis的字典也會有rehash的機制,保證其負載因子維持在合理的範圍內。

跳躍表 (skiplist)

Skiplist是一種應用非常廣的資料結構,通常是作為AVL樹的一種替代選擇,和AVL樹一樣,skiplist的查詢複雜度也是O(logn), 但是實現會簡單的多,下邊我們用短短的幾行字就能把SkipList的所有內容講的非常清楚。此外,在併發環境下,SkipList也會有很大優勢,因為AVL數在平衡過程中,可能會涉及到很多節點,也就需要鎖住很多節點,SkipList則完全不存在這種問題。

c737fe2aaee5184c819ef6aeb016fb8f.png

從網上找了一張示意圖,可以很清楚的展示出SkipList的結構。跳躍表說白了就是一個多層的列表,每一個元素會隨機的出現在某一層上,然後某一層的連結串列中會包含所有高於或等於本層的元素。

跳躍表的查詢就是從高層查起,逐步降層,定位到具體元素。比如要查詢7, 其順序就是9->6->7.

跳躍表的插入也是先做一次查詢,然後直接給元素設定一個隨機的層數,再調整指標。

刪除則是刪除節點,然後調整指標。

Redis中的有序集合,就是基於跳躍表實現的。

整數集合(intset)和壓縮列表(ziplist)

這兩個結構非常像,因此就放在一起講了。它們都是針對特定條件下的小資料集做的特定優化。

整數集合是一個有序集合,使用的條件是集合中只包含整數,且元素個數不多。

壓縮列表同樣是針對列表項非常少的情況,且要求元素只能是小整數值或短字串。它可以提供類似雙向連結串列的功能。

因為整數集合和壓縮列表都是針對小資料集的,所以可以使用連續的記憶體空間去儲存,實現也就簡單了很多,這裡就不細說了。

在實際應用中,zipList可以作為連結串列或者字典的替代品,應用在redis的列表、雜湊、有序集合中。整數集合則作為字典的替代品,用在集合物件中。

以上就是redis中主要的資料結構,在這些結構的基礎上,redis實現了大量功能完善的物件,供我們使用。理解了redis這些底層結構的原理,也可以幫助我們更好的發揮redis的價值。