本文快速回顧了Redis書籍、博客以及本人面試中遇到的基礎知識點,方便你們快速回顧知識。html
用做面試複習,事半功倍。git
分爲上下篇,上篇主要內容爲:程序員
基礎
概述
數據類型
數據結構
字典
跳躍表
使用場景
會話緩存
緩存
計數器
查找表
消息隊列
分佈式 Session
分佈式鎖
其它
Redis 與 Memcached 對比
數據類型
數據持久化
單線程
分佈式
內存管理機制
鍵的過時時間
數據淘汰策略github
全複習手冊文章導航:面試
點擊公衆號下方技術推文——面試衝刺算法
已發佈知識點複習手冊數據庫
Java基礎知識點面試手冊(上)緩存
Java基礎知識點面試手冊(下)安全
Java容器(List、Set、Map)知識點快速複習手冊(上)服務器
Java容器(List、Set、Map)知識點快速複習手冊(中)
Java容器(List、Set、Map)知識點快速複習手冊(下)
Redis 是速度很是快的非關係型(NoSQL)內存鍵值數據庫,能夠存儲鍵和五種不一樣類型的值之間的映射。
鍵的類型只能爲字符串
Redis 支持不少特性,例如將內存中的數據持久化到硬盤中,使用複製來擴展讀性能,使用分片來擴展寫性能。
image.png
1> set hello world 2OK 3> get hello 4"world" 5> del hello 6(integer) 1 7> get hello 8(nil)
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"
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"
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"
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
來看看跳躍表的複雜度分析:
相關操做的時間複雜度:
它是按層建造的。底層是一個普通的有序鏈表。每一個更高層都充當下面列表的「快速跑道」,這裏在層i中的元素按機率l/p出如今層i+1中。
平均起來,每一個元素都在p/(p-1)個列表中出現,而最高層的元素(一般是在跳躍列表前段的一個特殊的頭元素)在O(logp n)個列表中出現。
調節p的大小能夠在內存消耗和時間消耗上進行折中。
image.png
在查找時,從上層指針開始查找,找到對應的區間以後再到下一層去查找。下圖演示了查找 22 的過程。
image.png
與紅黑樹等平衡樹相比,跳躍表具備如下優勢:
在分佈式場景下具備多個應用服務器,可使用 Redis 來統一存儲這些應用服務器的會話信息。
當應用服務器再也不存儲用戶的會話信息,也就再也不具備狀態,一個用戶能夠請求任意一個應用服務器。
將熱點數據放到內存中,設置內存的最大使用量以及過時淘汰策略來保證緩存的命中率。
能夠對 String 進行自增自減運算,從而實現計數器功能。
Redis 這種內存型數據庫的讀寫性能很是高,很適合存儲頻繁讀寫的計數量。
例如 DNS 記錄就很適合使用 Redis 進行存儲。
查找表和緩存相似,也是利用了 Redis 快速的查找特性。可是查找表的內容不能失效,而緩存的內容能夠失效,由於緩存不做爲可靠的數據來源。
List 是一個雙向鏈表,能夠經過 lpop 和 lpush 寫入和讀取消息。
不過最好使用 Kafka、RabbitMQ 等消息中間件。
多個應用服務器的 Session 都存儲到 Redis 中來保證 Session 的一致性。
分佈式鎖實現
在分佈式場景下,沒法使用單機環境下的鎖來對多個節點上的進程進行同步。
可使用 Reids 自帶的 SETNX 命令實現分佈式鎖,除此以外,還可使用官方提供的 RedLock 分佈式鎖實現。
Set 能夠實現交集、並集等操做,從而實現共同好友等功能。
ZSet 能夠實現有序性操做,從而實現排行榜等功能。
image.png
Memcached 僅支持字符串類型,而 Redis 支持五種不一樣種類的數據類型,使得它能夠更靈活地解決問題。
Redis 支持兩種持久化策略:RDB 快照和 AOF 日誌,而 Memcached 不支持持久化。
http://www.javashuo.com/article/p-bzyaveid-ka.html
Redis快的主要緣由是:
徹底基於內存
數據結構簡單,對數據操做也簡單
使用多路 I/O 複用模型
單進程單線程好處
單進程單線程弊端
其餘一些優秀的開源軟件採用的模型
Memcached 不支持分佈式,只能經過在客戶端使用像一致性哈希這樣的分佈式算法來實現分佈式存儲,這種方式在存儲和查詢時都須要先在客戶端計算一次數據所在的節點。
Redis Cluster 實現了分佈式的支持。採用虛擬槽。(爲什麼不須要計算了?不懂)
在 Redis 中,並非全部數據都一直存儲在內存中,能夠將一些好久沒用的 value 交換到磁盤。而Memcached 的數據則會一直在內存中。
Memcached 將內存分割成特定長度的塊來存儲數據,以徹底解決內存碎片的問題,可是這種方式會使得內存的利用率不高,例如塊的大小爲 128 bytes,只存儲 100 bytes 的數據,那麼剩下的 28 bytes 就浪費掉了。
Redis 能夠爲每一個鍵設置過時時間,當鍵過時時,會自動刪除該鍵。
對於散列表這種容器,只能爲整個鍵設置過時時間(整個散列表),而不能爲鍵裏面的單個元素設置過時時間。
能夠設置內存最大使用量,當內存使用量超過期施行淘汰策略,具體有 6 種淘汰策略。
做爲內存數據庫,出於對性能和內存消耗的考慮,Redis 的淘汰算法實際實現上並不是針對全部 key,而是抽樣一小部分而且從中選出被淘汰的 key。
使用 Redis 緩存數據時,爲了提升緩存命中率,須要保證緩存數據都是熱點數據。能夠將內存最大使用量設置爲熱點數據佔用的內存量,而後啓用 allkeys-lru 淘汰策略,將最近最少使用的數據淘汰。
Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略經過統計訪問頻率,將訪問頻率最少的鍵值對淘汰。
https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Redis.md
我是蠻三刀把刀,目前爲後臺開發工程師。主要關注後臺開發,網絡安全,Python爬蟲等技術。
來微信和我聊聊:yangzd1102
Github:https://github.com/qqxx6661
擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發
https://www.zhihu.com/people/yang-zhen-dong-1/
擁有專欄:碼農面試助攻手冊
https://juejin.im/user/5b48015ce51d45191462ba55
https://www.jianshu.com/u/b5f225ca2376
我的公衆號:Rude3Knife我的公衆號:Rude3Knife若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~