redis 學習筆記

NoSQL Redis 數據結構 UNIX PHP愛好者 PHP愛好者 2017-11-04

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

redis 是什麼?

Redis是一種面向“鍵/值”對類型數據的分佈式NoSQL數據庫系統,特點是高性能,持久存儲,適應高併發的應用場景。

Redis 數據結構

動態字符串 (Sds)

雙端列表 (LINKEDLIST)

字典

跳躍表 (SKIPLIST)

整數集合 (INTSET)

壓縮列表 (ZIPLIST)

動態字符串

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

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

在 Redis 中, 一個字符串對象除了可以保存字符串值之外,還可以保存 long 類型的值當字符串對象保存的是字符串時,它包含的才是 sds 值,否則的話,它就 是一個 long 類型的值

動態字符串主要有兩個作用:

實現字符串對象(StringObject)

在 Redis 程序內部用作 char * 類型的替代品

雙端列表

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

雙端鏈表主要有兩個作用:

作為 Redis 列表類型的底層實現之一;

作為通用數據結構,被其他功能模塊所使用;

字典

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

字典的應用

實現數據庫鍵空間(key space);

用作 Hash 類型鍵的其中一種底層實現;

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

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

跳躍表

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

和字典、鏈表或者字符串這幾種在 Redis 中大量使用的數據結構不同,跳躍表在 Redis 的唯一作用,就是實現有序集數據類型

跳躍表將指向有序集的 score 值和 member 域的指針作為元素,並以 score 值為索引,對有序集元素進行排序。

整數集合

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

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

只保存著整數元素;

元素的數量不多;

那麼 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 執行以下步驟:

根據給定key,在數據庫字典中查找和它像對應的redisObject,如果沒找到,就返回 NULL 。

檢查redisObject的type屬性和執行命令所需的類型是否相符,如果不相符,返回類 型錯誤。

根據redisObject的encoding屬性所指定的編碼,選擇合適的操作函數來處理底層的 數據結構。

返回數據結構的操作結果作為命令的返回值。

字符串

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 學習筆記

過期時間

在數據庫中,所有鍵的過期時間都被保存在 redisDb 結構的 expires 字典裡:

typedef struct redisDb {

// ...

dict *expires;

// ...

} redisDb;

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

過期時間設置

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

EXPIRE 以秒為單位設置鍵的生存時間;

PEXPIRE 以毫秒為單位設置鍵的生存時間;

EXPIREAT 以秒為單位,設置鍵的過期 UNIX 時間戳;

PEXPIREAT 以毫秒為單位,設置鍵的過期 UNIX 時間戳。

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

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

下邊是參考答案

定時刪除:在設置鍵的過期時間時,創建一個定時事件,當過期時間到達時,由事件處理 器自動執行鍵的刪除操作。

惰性刪除:放任鍵過期不管,但是在每次從 dict 字典中取出鍵值時,要檢查鍵是否過 期,如果過期的話,就刪除它,並返回空;如果沒過期,就返回鍵值。

定期刪除:每隔一段時間,對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命令來做,他可以在不同集合之間取出交集

相關推薦

推薦中...