Redis 學習筆記

season0891發表於2010-12-10

這篇 redis 學習筆記主要介紹 redis 的資料結構和資料型別,並討論資料結構的選擇以及應用場景的優化。

redis 是什麼?

Redis是一種面向“鍵/值”對型別資料的分散式NoSQL資料庫系統,特點是高效能,持久儲存,適應高併發的應用場景。

Redis 資料結構

  • 動態字串 (Sds)
  • 雙端列表 (LINKEDLIST)
  • 字典
  • 跳躍表 (SKIPLIST)
  • 整數集合 (INTSET)
  • 壓縮列表 (ZIPLIST)

HUGOMORE42

動態字串

Sds (Simple Dynamic String,簡單動態字串)是 Redis 底層所使用的字串表示,它被用 在幾乎所有的 Redis 模組中

Redis 是一個鍵值對資料庫(key-value DB),資料庫的值可以是字串、集合、列表等多種類 型的物件,而資料庫的鍵則總是字串物件

在 Redis 中, 一個字串物件除了可以儲存字串值之外,還可以儲存 long 型別的值當字串物件儲存的是字串時,它包含的才是 sds 值,否則的話,它就 是一個 long 型別的值

動態字串主要有兩個作用:
  1. 實現字串物件(StringObject)
  2. 在 Redis 程式內部用作 char * 型別的替代品

雙端列表

雙端連結串列還是 Redis 列表型別的底層實現之一,當對列表型別的鍵進行操作——比如執行 RPUSH 、LPOP 或 LLEN 等命令時,程式在底層操作的可能就是雙端連結串列

雙端連結串列主要有兩個作用:
  • 作為 Redis 列表型別的底層實現之一;
  • 作為通用資料結構,被其他功能模組所使用;

字典

字典(dictionary),又名對映(map)或關聯陣列(associative array), 它是一種抽象資料結 構,由一集鍵值對(key-value pairs)組成,各個鍵值對的鍵各不相同,程式可以將新的鍵值對 新增到字典中,或者基於鍵進行查詢、更新或刪除等操作

字典的應用
  1. 實現資料庫鍵空間(key space);
  2. 用作 Hash 型別鍵的其中一種底層實現;

Redis 是一個鍵值對資料庫,資料庫中的鍵值對就由字典儲存:每個資料庫都有一個與之相對應的字典,這個字典被稱之為鍵空間(key space)。

Redis 的 Hash 型別鍵使用字典和壓縮列表兩種資料結構作為底層實現

跳躍表

跳躍表(skiplist)是一種隨機化的資料,由 William Pugh 在論文《Skip lists: a probabilistic alternative to balanced trees》中提出,這種資料結構以有序的方式在層次化的連結串列中儲存元素,它的效率可以和平衡樹媲美——查詢、刪除、新增等操作都可以在對數期望時間下完成, 並且比起平衡樹來說,跳躍表的實現要簡單直觀得多

和字典、連結串列或者字串這幾種在 Redis 中大量使用的資料結構不同,跳躍表在 Redis 的唯一作用,就是實現有序集資料型別
跳躍表將指向有序集的 score 值和 member 域的指標作為元素,並以 score 值為索引,對有序集元素進行排序。

整數集合

整數集合(intset)用於有序、無重複地儲存多個整數值,它會根據元素的值,自動選擇該用什麼長度的整數型別來儲存元素

Intset 是集合鍵的底層實現之一,如果一個集合:

  1. 只儲存著整數元素;
  2. 元素的數量不多;
    那麼 Redis 就會使用 intset 來儲存集合元素。

壓縮列表

Ziplist 是由一系列特殊編碼的記憶體塊構成的列表,一個 ziplist 可以包含多個節點(entry),每個節點可以儲存一個長度受限的字元陣列(不以 \0 結尾的 char 陣列)或者整數

Redis 資料型別

RedisObject

redisObject 是 Redis 型別系統的核心,資料庫中的每個鍵、值,以及 Redis 本身處理的引數,都表示為這種資料型別

redisObject 的定義位於 redis.h :

/*
* Redis 物件
*/
typedef struct redisObject {
    // 型別
    unsigned type:4;
    // 對齊位
    unsigned notused:2;
    // 編碼方式
    unsigned encoding:4;
    // LRU 時間(相對於 server.lruclock)
    unsigned lru:22;
    // 引用計數
    int refcount;
    // 指向物件的值
    void *ptr;
} robj;複製程式碼

type 、encoding 和 ptr 是最重要的三個屬性。

type 記錄了物件所儲存的值的型別,它的值可能是以下常量的其中一個

/*
* 物件型別
*/
#define REDIS_STRING 0 // 字串
#define REDIS_LIST 1   // 列表
#define REDIS_SET 2    // 集合
#define REDIS_ZSET 3   // 有序集
#define REDIS_HASH 4   // 雜湊表複製程式碼

encoding 記錄了物件所儲存的值的編碼,它的值可能是以下常量的其中一個

/*
* 物件編碼
*/
#define REDIS_ENCODING_RAW 0    // 編碼為字串
#define REDIS_ENCODING_INT 1    // 編碼為整數
#define REDIS_ENCODING_HT 2     // 編碼為雜湊表
#define REDIS_ENCODING_ZIPMAP 3 // 編碼為 zipmap(2.6 後不再使用)
#define REDIS_ENCODING_LINKEDLIST 4 // 編碼為雙端連結串列
#define REDIS_ENCODING_ZIPLIST 5    // 編碼為壓縮列表
#define REDIS_ENCODING_INTSET 6     // 編碼為整數集合
#define REDIS_ENCODING_SKIPLIST 7    // 編碼為跳躍表複製程式碼

ptr 是一個指標,指向實際儲存值的資料結構,這個資料結構由 type 屬性和 encoding 屬性決定。

當執行一個處理資料型別的命令時,Redis 執行以下步驟:

  1. 根據給定key,在資料庫字典中查詢和它像對應的redisObject,如果沒找到,就返回 NULL 。
  2. 檢查redisObject的type屬性和執行命令所需的型別是否相符,如果不相符,返回類 型錯誤。
  3. 根據redisObject的encoding屬性所指定的編碼,選擇合適的操作函式來處理底層的 資料結構。
  4. 返回資料結構的操作結果作為命令的返回值。

字串

REDIS_STRING (字串)是 Redis 使用得最為廣泛的資料型別,它除了是 SET 、GET 等命令 的操作物件之外,資料庫中的所有鍵,以及執行命令時提供給 Redis 的引數,都是用這種型別 儲存的。

字串型別分別使用 REDIS_ENCODING_INT 和 REDIS_ENCODING_RAW 兩種編碼

只有能表示為 long 型別的值,才會以整數的形式儲存,其他型別 的整數、小數和字串,都是用 sdshdr 結構來儲存

雜湊表

REDIS_HASH (雜湊表)是HSET 、HLEN 等命令的操作物件

它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_HT 兩種編碼方式

Redis 中每個hash可以儲存232-1鍵值對(40多億)

列表

REDIS_LIST(列表)是LPUSH 、LRANGE等命令的操作物件

它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_LINKEDLIST 這兩種方式編碼

一個列表最多可以包含232-1 個元素(4294967295, 每個列表超過40億個元素)。

集合

REDIS_SET (集合) 是 SADD 、 SRANDMEMBER 等命令的操作物件

它使用 REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT 兩種方式編碼

Redis 中集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1)。

集合中最大的成員數為 232 - 1 (4294967295, 每個集合可儲存40多億個成員)

有序集

REDIS_ZSET (有序集)是ZADD 、ZCOUNT 等命令的操作物件

它使用 REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_SKIPLIST 兩種方式編碼

不同的是每個元素都會關聯一個double型別的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。

有序集合的成員是唯一的,但分數(score)卻可以重複。

集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1)。 集合中最大的成員數為 232 - 1 (4294967295, 每個集合可儲存40多億個成員)

Redis各種資料型別_以及它們的編碼方式

Redis各種資料型別_以及它們的編碼方式
Redis各種資料型別_以及它們的編碼方式

過期時間

在資料庫中,所有鍵的過期時間都被儲存在 redisDb 結構的 expires 字典裡:

typedef struct redisDb {
    // ...
    dict *expires;
    // ...
} redisDb;複製程式碼

expires 字典的鍵是一個指向 dict 字典(鍵空間)裡某個鍵的指標,而字典的值則是鍵所指 向的資料庫鍵的到期時間,這個值以 long long 型別表示

過期時間設定

Redis 有四個命令可以設定鍵的生存時間(可以存活多久)和過期時間(什麼時候到期):

  • EXPIRE 以秒為單位設定鍵的生存時間;
  • PEXPIRE 以毫秒為單位設定鍵的生存時間;
  • EXPIREAT 以秒為單位,設定鍵的過期 UNIX 時間戳;
  • PEXPIREAT 以毫秒為單位,設定鍵的過期 UNIX 時間戳。

雖然有那麼多種不同單位和不同形式的設定方式,但是 expires 字典的值只儲存“以毫秒為單位的過期 UNIX 時間戳” ,這就是說,通過進行轉換,所有命令的效果最後都和 PEXPIREAT 命令的效果一樣。

如果一個鍵是過期的,那它什麼時候會被刪除?

下邊是參考答案

  1. 定時刪除:在設定鍵的過期時間時,建立一個定時事件,當過期時間到達時,由事件處理 器自動執行鍵的刪除操作。
  2. 惰性刪除:放任鍵過期不管,但是在每次從 dict 字典中取出鍵值時,要檢查鍵是否過 期,如果過期的話,就刪除它,並返回空;如果沒過期,就返回鍵值。
  3. 定期刪除:每隔一段時間,對expires字典進行檢查,刪除裡面的過期鍵

Redis 使用的過期鍵刪除策略是惰性刪除加上定期刪除

應用場景

  • 快取
  • 佇列
  • 需要精準設定過期時間的應用

比如你可以把上面說到的sorted set的score值設定成過期時間的時間戳,那麼就可以簡單地通過過期時間排序,定時清除過期資料了,不僅是清除Redis中的過期資料,你完全可以把Redis裡這個過期時間當成是對資料庫中資料的索引,用Redis來找出哪些資料需要過期刪除,然後再精準地從資料庫中刪除相應的記錄

  • 排行榜應用,取TOP N操作

    這個需求與上面需求的不同之處在於,前面操作以時間為權重,這個是以某個條件為權重,比如按頂的次數排序,這時候就需要我們的sorted set出馬了,將你要排序的值設定成sorted set的score,將具體的資料設定成相應的value,每次只需要執行一條ZADD命令即可

  • 統計頁面訪問次數

使用 incr 命令 定時使用 getset 命令 讀取資料 並設定新的值 0

  • 使用set 設定標籤

例如假設我們的話題D 1000被加了三個標籤tag 1,2,5和77,就可以設定下面兩個集合:

$ redis-cli sadd topics:1000:tags 1
(integer) 1
$ redis-cli sadd topics:1000:tags 2
(integer) 1
$ redis-cli sadd topics:1000:tags 5
(integer) 1
$ redis-cli sadd topics:1000:tags 77
(integer) 1
$ redis-cli sadd tag:1:objects 1000
(integer) 1
$ redis-cli sadd tag:2:objects 1000
(integer) 1
$ redis-cli sadd tag:5:objects 1000
(integer) 1
$ redis-cli sadd tag:77:objects 1000
(integer) 1複製程式碼

要獲取一個物件的所有標籤:

$ redis-cli smembers topics:1000:tags
1. 5
2. 1
3. 77
4. 2複製程式碼

獲得一份同時擁有標籤1, 2,10和27的物件列表。
這可以用SINTER命令來做,他可以在不同集合之間取出交集

記憶體優化

問題: Instagram的照片數量已經達到3億,而在Instagram裡,我們需要知道每一張照片的作者是誰,下面就是Instagram團隊如何使用Redis來解決這個問題並進行記憶體優化的。

具體方法,參考下邊這篇文章:節約記憶體:Instagram的Redis實踐

參考連結

最後,感謝女朋友支援。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注
歡迎關注
請我喝芬達
請我喝芬達

相關文章