1. 程式人生 > 資料庫 >Redis基礎知識點快速複習手冊(上)

Redis基礎知識點快速複習手冊(上)

前言

本文快速回顧了Redis書籍、部落格以及本人面試中遇到的基礎知識點,方便大家快速回顧知識。

用作面試複習,事半功倍。

分為上下篇,上篇主要內容為:

基礎
概述
資料型別
資料結構
字典
跳躍表
使用場景
會話快取
快取
計數器
查詢表
訊息佇列
分散式 Session
分散式鎖
其它
Redis 與 Memcached 對比
資料型別
資料持久化
單執行緒
分散式
記憶體管理機制
鍵的過期時間
資料淘汰策略

面試知識點複習手冊

全複習手冊文章導航:

點選公眾號下方技術推文——面試衝刺

已釋出知識點複習手冊

Java基礎知識點面試手冊(上)

Java基礎知識點面試手冊(下)

Java容器(List、Set、Map)知識點快速複習手冊(上)

Java容器(List、Set、Map)知識點快速複習手冊(中)

Java容器(List、Set、Map)知識點快速複習手冊(下)

基礎

概述

Redis 是速度非常快的非關係型(NoSQL)記憶體鍵值資料庫,可以儲存鍵和五種不同型別的值之間的對映。

  • 鍵的型別只能為字串

  • 值支援的五種型別資料型別為:字串、列表、集合、有序集合、散列表。

Redis 支援很多特性,例如將記憶體中的資料持久化到硬碟中,使用複製來擴充套件讀效能,使用分片來擴充套件寫效能。

資料型別

Redis基礎知識點快速複習手冊(上)

STRING

Redis基礎知識點快速複習手冊(上)
image.png

1> set hello world
2OK
3> get hello
4"world"
5> del hello
6(integer) 1
7> get hello
8(nil)

LIST

Redis基礎知識點快速複習手冊(上)
image.png

 1> rpush list-key item
 2(integer) 1
 3> rpush list-key item2
 4(integer) 2
 5> rpush list-key item
 6(integer) 3
 7
 8> lrange list-key 0 -1
 91) "item"
102) "item2"
113) "item"
12
13> lindex list-key 1
14"item2"
15
16> lpop list-key
17"item"
18
19> lrange list-key 0 -1
201) "item2"
212) "item"

SET

Redis基礎知識點快速複習手冊(上)
image.png

 1> sadd set-key item
 2(integer) 1
 3> sadd set-key item2
 4(integer) 1
 5> sadd set-key item3
 6(integer) 1
 7> sadd set-key item
 8(integer) 0
 9
10> smembers set-key
111) "item"
122) "item2"
133) "item3"
14
15> sismember set-key item4
16(integer) 0
17> sismember set-key item
18(integer) 1
19
20> srem set-key item2
21(integer) 1
22> srem set-key item2
23(integer) 0
24
25> smembers set-key
261) "item"
272) "item3"

HASH

Redis基礎知識點快速複習手冊(上)
image.png

 1> hset hash-key sub-key1 value1
 2(integer) 1
 3> hset hash-key sub-key2 value2
 4(integer) 1
 5> hset hash-key sub-key1 value1
 6(integer) 0
 7
 8> hgetall hash-key
 91) "sub-key1"
102) "value1"
113) "sub-key2"
124) "value2"
13
14> hdel hash-key sub-key2
15(integer) 1
16> hdel hash-key sub-key2
17(integer) 0
18
19> hget hash-key sub-key1
20"value1"
21
22> hgetall hash-key
231) "sub-key1"
242) "value1"

ZSET(SORTEDSET)

Redis基礎知識點快速複習手冊(上)
image.png

 1> zadd zset-key 728 member1
 2(integer) 1
 3> zadd zset-key 982 member0
 4(integer) 1
 5> zadd zset-key 982 member0
 6(integer) 0
 7
 8> zrange zset-key 0 -1 withscores
 91) "member1"
102) "728"
113) "member0"
124) "982"
13
14> zrangebyscore zset-key 0 800 withscores
151) "member1"
162) "728"
17
18> zrem zset-key member1
19(integer) 1
20> zrem zset-key member1
21(integer) 0
22
23> zrange zset-key 0 -1 withscores
241) "member0"
252) "982"

zset是set的一個升級版本,他在set的基礎上增加了一個順序屬性,這一屬性在新增修改元素的時候可以指定,每次指定後,zset會自動重新按新的值調整順序。 可以對指定鍵的值進行排序權重的設定,它應用排名模組比較多。

跳躍表(shiplist)是實現sortset(有序集合)的底層資料結構之一

另外還可以用 Sorted Sets 來做帶權重的佇列,比如普通訊息的 score 為1,重要訊息的 score 為2,然後工作執行緒可以選擇按 score的倒序來獲取工作任務,讓重要的任務優先執行。

資料結構

字典

dictht 是一個散列表結構,使用拉鍊法儲存雜湊衝突的 dictEntry。

1/* This is our hash table structure. Every dictionary has two of this as we
2 * implement incremental rehashing, for the old to the new table. */
3typedef struct dictht {
4    dictEntry **table;
5    unsigned long size;
6    unsigned long sizemask;
7    unsigned long used;
8} dictht;
 1typedef struct dictEntry {
 2    void *key;
 3    union {
 4        void *val;
 5        uint64_t u64;
 6        int64_t s64;
 7        double d;
 8    } v;
 9    struct dictEntry *next;
10} dictEntry;

Redis 的字典 dict 中包含兩個雜湊表 dictht,這是為了方便進行 rehash 操作。

在擴容時,將其中一個 dictht 上的鍵值對 rehash 到另一個 dictht 上面,完成之後釋放空間並交換兩個 dictht 的角色。

1typedef struct dict {
2    dictType *type;
3    void *privdata;
4    dictht ht[2];
5    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
6    unsigned long iterators; /* number of iterators currently running */
7} dict;

rehash 操作不是一次性完成,而是採用漸進方式,這是為了避免一次性執行過多的 rehash 操作給伺服器帶來過大的負擔。

漸進式 rehash 通過記錄 dict 的 rehashidx 完成,它從 0 開始,然後每執行一次 rehash 都會遞增。例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],這一次會把 dict[0] 上 table[rehashidx] 的鍵值對 rehash 到 dict[1] 上,dict[0] 的 table[rehashidx] 指向 null,並令 rehashidx++。

在 rehash 期間,每次對字典執行新增、刪除、查詢或者更新操作時,都會執行一次漸進式 rehash。

採用漸進式 rehash 會導致字典中的資料分散在兩個 dictht 上,因此對字典的操作也需要到對應的 dictht 去執行。

 1/* Performs N steps of incremental rehashing. Returns 1 if there are still
 2 * keys to move from the old to the new hash table, otherwise 0 is returned.
 3 *
 4 * Note that a rehashing step consists in moving a bucket (that may have more
 5 * than one key as we use chaining) from the old to the new hash table, however
 6 * since part of the hash table may be composed of empty spaces, it is not
 7 * guaranteed that this function will rehash even a single bucket, since it
 8 * will visit at max N*10 empty buckets in total, otherwise the amount of
 9 * work it does would be unbound and the function may block for a long time. */
10int dictRehash(dict *d, int n) {
11    int empty_visits = n * 10; /* Max number of empty buckets to visit. */
12    if (!dictIsRehashing(d)) return 0;
13
14    while (n-- && d->ht[0].used != 0) {
15        dictEntry *de, *nextde;
16
17        /* Note that rehashidx can't overflow as we are sure there are more
18         * elements because ht[0].used != 0 */
19        assert(d->ht[0].size > (unsigned long) d->rehashidx);
20        while (d->ht[0].table[d->rehashidx] == NULL) {
21            d->rehashidx++;
22            if (--empty_visits == 0) return 1;
23        }
24        de = d->ht[0].table[d->rehashidx];
25        /* Move all the keys in this bucket from the old to the new hash HT */
26        while (de) {
27            uint64_t h;
28
29            nextde = de->next;
30            /* Get the index in the new hash table */
31            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
32            de->next = d->ht[1].table[h];
33            d->ht[1].table[h] = de;
34            d->ht[0].used--;
35            d->ht[1].used++;
36            de = nextde;
37        }
38        d->ht[0].table[d->rehashidx] = NULL;
39        d->rehashidx++;
40    }
41
42    /* Check if we already rehashed the whole table... */
43    if (d->ht[0].used == 0) {
44        zfree(d->ht[0].table);
45        d->ht[0] = d->ht[1];
46        _dictReset(&d->ht[1]);
47        d->rehashidx = -1;
48        return 0;
49    }
50
51    /* More to rehash... */
52    return 1;
53}

跳躍表

什麼是跳躍表?(程式設計師小灰)

來看看跳躍表的複雜度分析:

  • 空間複雜度: O(n) (期望)
  • 跳躍表高度: O(logn) (期望)

相關操作的時間複雜度:

  • 查詢: O(logn) (期望)
  • 插入: O(logn) (期望)
  • 刪除: O(logn) (期望)
    其效率可比擬於二叉查詢樹(對於大於數操作需要O(log n)平均時間),並且不需要像二叉樹一樣過段時間重新平衡。

它是按層建造的。底層是一個普通的有序連結串列。每個更高層都充當下面列表的“快速跑道”,這裡在層i中的元素按概率l/p出現在層i+1中。

平均起來,每個元素都在p/(p-1)個列表中出現,而最高層的元素(通常是在跳躍列表前段的一個特殊的頭元素)在O(logp n)個列表中出現。

調節p的大小可以在記憶體消耗和時間消耗上進行折中。
Redis基礎知識點快速複習手冊(上)
image.png

在查詢時,從上層指標開始查詢,找到對應的區間之後再到下一層去查詢。下圖演示了查詢 22 的過程。
Redis基礎知識點快速複習手冊(上)
image.png

與紅黑樹等平衡樹相比,跳躍表具有以下優點:

  • 插入速度非常快速,因為不需要進行旋轉等操作來維護平衡性;
  • 更容易實現;
  • 支援無鎖操作。

使用場景

會話快取

在分散式場景下具有多個應用伺服器,可以使用 Redis 來統一儲存這些應用伺服器的會話資訊。

當應用伺服器不再儲存使用者的會話資訊,也就不再具有狀態,一個使用者可以請求任意一個應用伺服器。

快取

將熱點資料放到記憶體中,設定記憶體的最大使用量以及過期淘汰策略來保證快取的命中率。

計數器

可以對 String 進行自增自減運算,從而實現計數器功能。

Redis 這種記憶體型資料庫的讀寫效能非常高,很適合儲存頻繁讀寫的計數量。

查詢表

例如 DNS 記錄就很適合使用 Redis 進行儲存。

查詢表和快取類似,也是利用了 Redis 快速的查詢特性。但是查詢表的內容不能失效,而快取的內容可以失效,因為快取不作為可靠的資料來源。

訊息佇列

List 是一個雙向連結串列,可以通過 lpop 和 lpush 寫入和讀取訊息。

不過最好使用 Kafka、RabbitMQ 等訊息中介軟體。

分散式 Session

多個應用伺服器的 Session 都儲存到 Redis 中來保證 Session 的一致性。

分散式鎖

分散式鎖實現
在分散式場景下,無法使用單機環境下的鎖來對多個節點上的程序進行同步。

可以使用 Reids 自帶的 SETNX 命令實現分散式鎖,除此之外,還可以使用官方提供的 RedLock 分散式鎖實現。

其它

Set 可以實現交集、並集等操作,從而實現共同好友等功能。

ZSet 可以實現有序性操作,從而實現排行榜等功能。

Redis 與 Memcached 對比

Redis基礎知識點快速複習手冊(上)
image.png

資料型別

Memcached 僅支援字串型別,而 Redis 支援五種不同種類的資料型別,使得它可以更靈活地解決問題。

資料持久化

Redis 支援兩種持久化策略:RDB 快照和 AOF 日誌,而 Memcached 不支援持久化。

單執行緒

Redis快的主要原因是:

  • 完全基於記憶體

  • 資料結構簡單,對資料操作也簡單

  • 使用多路 I/O 複用模型

  • 單程序單執行緒好處

  • 程式碼更清晰,處理邏輯更簡單
  • 不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗
  • 不存在多程序或者多執行緒導致的切換而消耗CPU
  • 單程序單執行緒弊端

  • 無法發揮多核CPU效能,不過可以通過在單機開多個Redis例項來完善;
  • 其他一些優秀的開源軟體採用的模型

  • 多程序單執行緒模型:Nginx
  • 單程序多執行緒模型:Memcached
    Redis基礎知識點快速複習手冊(上)
    image.png

分散式

Memcached 不支援分散式,只能通過在客戶端使用像一致性雜湊這樣的分散式演算法來實現分散式儲存,這種方式在儲存和查詢時都需要先在客戶端計算一次資料所在的節點。

Redis Cluster 實現了分散式的支援。採用虛擬槽。(為何不需要計算了?不懂)

記憶體管理機制

在 Redis 中,並不是所有資料都一直儲存在記憶體中,可以將一些很久沒用的 value 交換到磁碟。而Memcached 的資料則會一直在記憶體中。

Memcached 將記憶體分割成特定長度的塊來儲存資料,以完全解決記憶體碎片的問題,但是這種方式會使得記憶體的利用率不高,例如塊的大小為 128 bytes,只儲存 100 bytes 的資料,那麼剩下的 28 bytes 就浪費掉了。

鍵的過期時間

Redis 可以為每個鍵設定過期時間,當鍵過期時,會自動刪除該鍵。

對於散列表這種容器,只能為整個鍵設定過期時間(整個散列表),而不能為鍵裡面的單個元素設定過期時間。

資料淘汰策略

可以設定記憶體最大使用量,當記憶體使用量超過時施行淘汰策略,具體有 6 種淘汰策略。
Redis基礎知識點快速複習手冊(上)

作為記憶體資料庫,出於對效能和記憶體消耗的考慮,Redis 的淘汰演算法實際實現上並非針對所有 key,而是抽樣一小部分並且從中選出被淘汰的 key。

使用 Redis 快取資料時,為了提高快取命中率,需要保證快取資料都是熱點資料。可以將記憶體最大使用量設定為熱點資料佔用的記憶體量,然後啟用 allkeys-lru 淘汰策略,將最近最少使用的資料淘汰。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通過統計訪問頻率,將訪問頻率最少的鍵值對淘汰。

參考與拓展閱讀

  • 【3y】從零單排學Redis【青銅】

關注我

我是蠻三刀把刀,目前為後臺開發工程師。主要關注後臺開發,網路安全,Python爬蟲等技術。

來微信和我聊聊:yangzd1102

Github:

原創部落格主要內容

  • 筆試面試複習知識點手冊
  • Leetcode演算法題解析(前150題)
  • 劍指offer演算法題解析
  • Python爬蟲相關實戰
  • 後臺開發相關實戰
    同步更新以下部落格
  1. Csdn

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發

  1. 知乎

擁有專欄:碼農面試助攻手冊

  1. 掘金

  1. 簡書

個人公眾號:Rude3Knife

Redis基礎知識點快速複習手冊(上)
個人公眾號:Rude3Knife
個人公眾號:Rude3Knife
如果文章對你有幫助,不妨收藏起來並轉發給您的朋友們~