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

前言

本文快速回顧了Redis書籍、博客以及本人面試中遇到的基礎知識點,方便你們快速回顧知識。html

用做面試複習,事半功倍。git

分爲上下篇,上篇主要內容爲:程序員

基礎
概述
數據類型
數據結構
字典
跳躍表
使用場景
會話緩存
緩存
計數器
查找表
消息隊列
分佈式 Session
分佈式鎖
其它
Redis 與 Memcached 對比
數據類型
數據持久化
單線程
分佈式
內存管理機制
鍵的過時時間
數據淘汰策略github

面試知識點複習手冊

全複習手冊文章導航:面試

點擊公衆號下方技術推文——面試衝刺算法

已發佈知識點複習手冊數據庫

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}

跳躍表

什麼是跳躍表?(程序員小灰)http://blog.jobbole.com/111731/

https://blog.csdn.net/qq910894904/article/details/37883953

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

  • 空間複雜度: 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 不支持持久化。

單線程

http://www.javashuo.com/article/p-bzyaveid-ka.html

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 策略經過統計訪問頻率,將訪問頻率最少的鍵值對淘汰。

參考與拓展閱讀

關注我

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

來微信和我聊聊:yangzd1102

Github:https://github.com/qqxx6661

原創博客主要內容

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

http://blog.csdn.net/qqxx6661

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

  1. 知乎

https://www.zhihu.com/people/yang-zhen-dong-1/

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

  1. 掘金

https://juejin.im/user/5b48015ce51d45191462ba55

  1. 簡書

https://www.jianshu.com/u/b5f225ca2376

我的公衆號:Rude3Knife

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

相關文章
相關標籤/搜索