redis 學習筆記

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

redis 是什麼?

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

Redis 數據結構

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

動態字符串

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

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

在 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)。bash

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各類數據類型_以及它們的編碼方式

過時時間

在數據庫中,全部鍵的過時時間都被保存在 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) 請我喝芬達
歡迎關注 請我喝芬達
相關文章
相關標籤/搜索