Redis基礎知識點面試手冊

Redis基礎知識點面試手冊

 

 

 

 

 

 

 

 

 

 

 

基礎

概述

Redis 是速度很是快的非關係型(NoSQL)內存鍵值數據庫,能夠存儲鍵和五種不一樣類型的值之間的映射。

  • 鍵的類型只能爲字符串

  • 值支持的五種類型數據類型爲:字符串、列表、集合、有序集合、散列表。

Redis 支持不少特性,例如將內存中的數據持久化到硬盤中,使用複製來擴展讀性能,使用分片來擴展寫性能。

數據類型

數據類型能夠存儲的值操做STRING字符串、整數或者浮點數對整個字符串或者字符串的其中一部分執行操做</br> 對整數和浮點數執行自增或者自減操做LIST列表從兩端壓入或者彈出元素</br> 讀取單個或者多個元素</br> 進行修剪,只保留一個範圍內的元素SET無序集合添加、獲取、移除單個元素</br> 檢查一個元素是否存在於集合中</br> 計算交集、並集、差集</br> 從集合裏面隨機獲取元素HASH包含鍵值對的無序散列表添加、獲取、移除單個鍵值對</br> 獲取全部鍵值對</br> 檢查某個鍵是否存在ZSET有序集合添加、獲取、刪除元素</br> 根據分值範圍或者成員來獲取元素</br> 計算一個鍵的排名

STRING

 

image.png

> set hello world
OK
> get hello
"world"
> del hello
(integer) 1
> get hello
(nil)

LIST

 

image.png

> rpush list-key item
(integer) 1
> rpush list-key item2
(integer) 2
> rpush list-key item
(integer) 3

> lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"

> lindex list-key 1
"item2"

> lpop list-key
"item"

> lrange list-key 0 -1
1) "item2"
2) "item"

SET

 

image.png

> sadd set-key item
(integer) 1
> sadd set-key item2
(integer) 1
> sadd set-key item3
(integer) 1
> sadd set-key item
(integer) 0

> smembers set-key
1) "item"
2) "item2"
3) "item3"

> sismember set-key item4
(integer) 0
> sismember set-key item
(integer) 1

> srem set-key item2
(integer) 1
> srem set-key item2
(integer) 0

> smembers set-key
1) "item"
2) "item3"

HASH

 

image.png

> hset hash-key sub-key1 value1
(integer) 1
> hset hash-key sub-key2 value2
(integer) 1
> hset hash-key sub-key1 value1
(integer) 0

> hgetall hash-key
1) "sub-key1"
2) "value1"
3) "sub-key2"
4) "value2"

> hdel hash-key sub-key2
(integer) 1
> hdel hash-key sub-key2
(integer) 0

> hget hash-key sub-key1
"value1"

> hgetall hash-key
1) "sub-key1"
2) "value1"

ZSET(SORTEDSET)

 

image.png

> zadd zset-key 728 member1
(integer) 1
> zadd zset-key 982 member0
(integer) 1
> zadd zset-key 982 member0
(integer) 0

> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "982"

> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"

> zrem zset-key member1
(integer) 1
> zrem zset-key member1
(integer) 0

> zrange zset-key 0 -1 withscores
1) "member0"
2) "982"

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

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

另外還能夠用 Sorted Sets 來作帶權重的隊列,好比普通消息的 score 爲1,重要消息的 score 爲2,而後工做線程能夠選擇按 score的倒序來獲取工做任務,讓重要的任務優先執行。

數據結構

字典

dictht 是一個散列表結構,使用拉鍊法保存哈希衝突的 dictEntry。

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

Redis 的字典 dict 中包含兩個哈希表 dictht,這是爲了方便進行 rehash 操做。

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

typedef struct dict {
   dictType *type;
   void *privdata;
   dictht ht[2];
   long rehashidx; /* rehashing not in progress if rehashidx == -1 */
   unsigned long iterators; /* number of iterators currently running */
} 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 去執行。

/* Performs N steps of incremental rehashing. Returns 1 if there are still
* keys to move from the old to the new hash table, otherwise 0 is returned.
*
* Note that a rehashing step consists in moving a bucket (that may have more
* than one key as we use chaining) from the old to the new hash table, however
* since part of the hash table may be composed of empty spaces, it is not
* guaranteed that this function will rehash even a single bucket, since it
* will visit at max N*10 empty buckets in total, otherwise the amount of
* work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
   int empty_visits = n * 10; /* Max number of empty buckets to visit. */
   if (!dictIsRehashing(d)) return 0;

   while (n-- && d->ht[0].used != 0) {
       dictEntry *de, *nextde;

       /* Note that rehashidx can't overflow as we are sure there are more
        * elements because ht[0].used != 0 */
       assert(d->ht[0].size > (unsigned long) d->rehashidx);
       while (d->ht[0].table[d->rehashidx] == NULL) {
           d->rehashidx++;
           if (--empty_visits == 0) return 1;
      }
       de = d->ht[0].table[d->rehashidx];
       /* Move all the keys in this bucket from the old to the new hash HT */
       while (de) {
           uint64_t h;

           nextde = de->next;
           /* Get the index in the new hash table */
           h = dictHashKey(d, de->key) & d->ht[1].sizemask;
           de->next = d->ht[1].table[h];
           d->ht[1].table[h] = de;
           d->ht[0].used--;
           d->ht[1].used++;
           de = nextde;
      }
       d->ht[0].table[d->rehashidx] = NULL;
       d->rehashidx++;
  }

   /* Check if we already rehashed the whole table... */
   if (d->ht[0].used == 0) {
       zfree(d->ht[0].table);
       d->ht[0] = d->ht[1];
       _dictReset(&d->ht[1]);
       d->rehashidx = -1;
       return 0;
  }

   /* More to rehash... */
   return 1;
}

跳躍表

什麼是跳躍表?(程序員小灰)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的大小能夠在內存消耗和時間消耗上進行折中。

 

image.png

在查找時,從上層指針開始查找,找到對應的區間以後再到下一層去查找。下圖演示了查找 22 的過程。

 

image.png

與紅黑樹等平衡樹相比,跳躍表具備如下優勢:

  • 插入速度很是快速,由於不須要進行旋轉等操做來維護平衡性;

  • 更容易實現

  • 支持無鎖操做

使用場景

會話緩存

在分佈式場景下具備多個應用服務器,可使用 Redis 來統一存儲這些應用服務器的會話信息。

當應用服務器再也不存儲用戶的會話信息,也就再也不具備狀態,一個用戶能夠請求任意一個應用服務器。

緩存

將熱點數據放到內存中,設置內存的最大使用量以及過時淘汰策略來保證緩存的命中率。

計數器

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

Redis 這種內存型數據庫的讀寫性能很是高,很適合存儲頻繁讀寫的計數量。

查找表

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

查找表和緩存相似,也是利用了 Redis 快速的查找特性。可是查找表的內容不能失效,而緩存的內容能夠失效,由於緩存不做爲可靠的數據來源。

消息隊列

List 是一個雙向鏈表,能夠經過 lpop 和 lpush 寫入和讀取消息。

不過最好使用 Kafka、RabbitMQ 等消息中間件。

分佈式 Session

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

分佈式鎖

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

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

其它

Set 能夠實現交集、並集等操做,從而實現共同好友等功能。

ZSet 能夠實現有序性操做,從而實現排行榜等功能。

Redis 與 Memcached 對比

 

image.png

數據類型

Memcached 僅支持字符串類型,而 Redis 支持五種不一樣種類的數據類型,使得它能夠更靈活地解決問題。

數據持久化

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

單線程

https://www.cnblogs.com/syyong/p/6231326.html

Redis快的主要緣由是:

  • 徹底基於內存

  • 數據結構簡單,對數據操做也簡單

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

  • 單進程單線程好處

    • 代碼更清晰,處理邏輯更簡單

    • 不用去考慮各類鎖的問題,不存在加鎖釋放鎖操做,沒有由於可能出現死鎖而致使的性能消耗

    • 不存在多進程或者多線程致使的切換而消耗CPU

 

  • 單進程單線程弊端

    • 沒法發揮多核CPU性能,不過能夠經過在單機開多個Redis實例來完善;

 

  • 其餘一些優秀的開源軟件採用的模型

    • 多進程單線程模型:Nginx

    • 單進程多線程模型:Memcached

 

 

 

分佈式

Memcached 不支持分佈式,只能經過在客戶端使用像一致性哈希這樣的分佈式算法來實現分佈式存儲,這種方式在存儲和查詢時都須要先在客戶端計算一次數據所在的節點

Redis Cluster 實現了分佈式的支持。採用虛擬槽。(爲什麼不須要計算了?不懂)

內存管理機制

在 Redis 中,並非全部數據都一直存儲在內存中,能夠將一些好久沒用的 value 交換到磁盤。而Memcached 的數據則會一直在內存中。

Memcached 將內存分割成特定長度的塊來存儲數據,以徹底解決內存碎片的問題,可是這種方式會使得內存的利用率不高,例如塊的大小爲 128 bytes,只存儲 100 bytes 的數據,那麼剩下的 28 bytes 就浪費掉了。

鍵的過時時間

Redis 能夠爲每一個鍵設置過時時間,當鍵過時時,會自動刪除該鍵。

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

數據淘汰策略

能夠設置內存最大使用量,當內存使用量超過期施行淘汰策略,具體有 6 種淘汰策略。

策略描述volatile-lru從已設置過時時間的數據集中挑選最近最少使用的數據淘汰volatile-ttl從已設置過時時間的數據集中挑選將要過時的數據淘汰volatile-random從已設置過時時間的數據集中任意選擇數據淘汰allkeys-lru從全部數據集中挑選最近最少使用的數據淘汰allkeys-random從全部數據集中任意選擇數據進行淘汰noeviction禁止驅逐數據

做爲內存數據庫,出於對性能和內存消耗的考慮,Redis 的淘汰算法實際實現上並不是針對全部 key,而是抽樣一小部分而且從中選出被淘汰的 key。

使用 Redis 緩存數據時,爲了提升緩存命中率,須要保證緩存數據都是熱點數據。能夠將內存最大使用量設置爲熱點數據佔用的內存量,而後啓用 allkeys-lru 淘汰策略,將最近最少使用的數據淘汰。

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

持久化

Redis 是內存型數據庫,爲了保證數據在斷電後不會丟失,須要將內存中的數據持久化到硬盤上。

https://my.oschina.net/davehe/blog/174662

RDB 快照持久化

將某個時間點的全部數據都存放到硬盤上。

能夠將快照複製到其它服務器從而建立具備相同數據的服務器副本。

若是系統發生故障,將會丟失最後一次建立快照以後的數據。

若是數據量很大,保存快照的時間會很長。

AOF 持久化

將寫命令添加到 AOF 文件(Append Only File)的末尾。

對硬盤的文件進行寫入時,寫入的內容首先會被存儲到緩衝區,而後由操做系統決定何時將該內容同步到硬盤,用戶能夠調用 file.flush() 方法請求操做系統儘快將緩衝區存儲的數據同步到硬盤。能夠看出寫入文件的數據不會當即同步到硬盤上,在將寫命令添加到 AOF 文件時,要根據需求來保證什麼時候同步到硬盤上。

有如下同步選項:

選項同步頻率always每一個寫命令都同步everysec每秒同步一次no讓操做系統來決定什麼時候同步

  • always 選項會嚴重減低服務器的性能;

  • everysec 選項比較合適,能夠保證系統奔潰時只會丟失一秒左右的數據,而且 Redis 每秒執行一次同步對服務器性能幾乎沒有任何影響;

  • no 選項並不能給服務器性能帶來多大的提高,並且也會增長系統奔潰時數據丟失的數量。

隨着服務器寫請求的增多,AOF 文件會愈來愈大。Redis 提供了一種將 AOF 重寫的特性,可以去除 AOF 文件中的冗餘寫命令,使得 AOF 文件的體積不會超出保存數據集狀態所需的實際大小。

若是 AOF 文件出錯了,怎麼辦?

服務器可能在程序正在對 AOF 文件進行寫入時停機, 若是停機形成了 AOF 文件出錯(corrupt), 那麼 Redis 在重啓時會拒絕載入這個 AOF 文件, 從而確保數據的一致性不會被破壞。

發佈與訂閱

訂閱者訂閱了頻道以後,發佈者向頻道發送字符串消息會被全部訂閱者接收到。

某個客戶端使用 SUBSCRIBE 訂閱一個頻道,其它客戶端可使用 PUBLISH 向這個頻道發送消息。

發佈與訂閱模式和觀察者模式有如下不一樣:

  • 觀察者模式中,觀察者和主題都知道對方的存在;而在發佈與訂閱模式中,發佈者與訂閱者不知道對方的存在,它們之間經過頻道進行通訊。

  • 觀察者模式是同步的,當事件觸發時,主題會去調用觀察者的方法;而發佈與訂閱模式是異步的;

事務

http://www.runoob.com/redis/redis-transactions.html

事務中的多個命令被一次性發送給服務器,而不是一條一條發送,這種方式被稱爲流水線,它能夠減小客戶端與服務器之間的網絡通訊次數從而提高性能。

Redis 最簡單的事務實現方式是使用 MULTI 和 EXEC 命令將事務操做包圍起來。

它先以 MULTI 開始一個事務, 而後將多個命令入隊到事務中, 最後由 EXEC 命令觸發事務, 一併執行事務中的全部命令

單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增長任何維持原子性的機制,因此 Redis 事務的執行並非原子性的。

事務能夠理解爲一個打包的批量執行腳本,但批量指令並不是原子化的操做,中間某條指令的失敗不會致使前面已作指令的回滾,也不會形成後續的指令不作。

事件

Redis 服務器是一個事件驅動程序。

文件事件

服務器經過套接字與客戶端或者其它服務器進行通訊,文件事件就是對套接字操做的抽象。

 

 

Redis 基於 Reactor 模式開發了本身的網絡時間處理器,使用 I/O 多路複用程序來同時監聽多個套接字,並將到達的時間傳送給文件事件分派器,分派器會根據套接字產生的事件類型調用響應的時間處理器。

時間事件

服務器有一些操做須要在給定的時間點執行,時間事件是對這類定時操做的抽象。

時間事件又分爲:

  • 定時事件:是讓一段程序在指定的時間以內執行一次;

  • 週期性事件:是讓一段程序每隔指定時間就執行一次。

Redis 將全部時間事件都放在一個無序鏈表中,經過遍歷整個鏈表查找出已到達的時間事件,並調用響應的事件處理器。

事件的調度與執行

服務器須要不斷監聽文件事件的套接字才能獲得待處理的文件事件,可是不能一直監聽,不然時間事件沒法在規定的時間內執行,所以監聽時間應該根據距離如今最近的時間事件來決定。

事件調度與執行由 aeProcessEvents 函數負責,僞代碼以下:

def aeProcessEvents():
   # 獲取到達時間離當前時間最接近的時間事件
   time_event = aeSearchNearestTimer()
   # 計算最接近的時間事件距離到達還有多少毫秒
   remaind_ms = time_event.when - unix_ts_now()
   # 若是事件已到達,那麼 remaind_ms 的值可能爲負數,將它設爲 0
   if remaind_ms < 0:
       remaind_ms = 0
   # 根據 remaind_ms 的值,建立 timeval
   timeval = create_timeval_with_ms(remaind_ms)
   # 阻塞並等待文件事件產生,最大阻塞時間由傳入的 timeval 決定
   aeApiPoll(timeval)
   # 處理全部已產生的文件事件
   procesFileEvents()
   # 處理全部已到達的時間事件
   processTimeEvents()

將 aeProcessEvents 函數置於一個循環裏面,加上初始化和清理函數,就構成了 Redis 服務器的主函數,僞代碼以下:

def main():
   # 初始化服務器
   init_server()
   # 一直處理事件,直到服務器關閉爲止
   while server_is_not_shutdown():
       aeProcessEvents()
   # 服務器關閉,執行清理操做
   clean_server()

從事件處理的角度來看,服務器運行流程以下:

 

image.png

複製(加強讀性能)

經過使用 slaveof host port 命令來讓一個服務器成爲另外一個服務器的從服務器。

一個從服務器只能有一個主服務器,而且不支持主主複製。

鏈接過程

  1. 主服務器建立快照文件,發送給從服務器,並在發送期間使用緩衝區記錄執行的寫命令。快照文件發送完畢以後,開始向從服務器發送存儲在緩衝區中的寫命令;

  2. 從服務器丟棄全部舊數據,載入主服務器發來的快照文件,以後從服務器開始接受主服務器發來的寫命令;

  3. 主服務器每執行一次寫命令,就向從服務器發送相同的寫命令。

主從鏈

隨着負載不斷上升,主服務器可能沒法很快地更新全部從服務器,或者從新鏈接和從新同步從服務器將致使系統超載。爲了解決這個問題,能夠建立一箇中間層來分擔主服務器的複製工做。中間層的服務器是最上層服務器的從服務器,又是最下層服務器的主服務器。

 

image.png

Sentinel(哨兵)

Sentinel(哨兵)能夠監聽主服務器,並在主服務器進入下線狀態時,自動從從服務器中選舉出新的主服務器。

分片(加強寫性能)

分片是將數據劃分爲多個部分的方法,能夠將數據存儲到多臺機器裏面.

主要有三種分片方式:

  • 客戶端分片:客戶端使用一致性哈希等算法決定鍵應當分佈到哪一個節點。

  • 代理分片:將客戶端請求發送到代理上,由代理轉發請求到正確的節點上。

  • 服務器分片:Redis Cluster。

Redis-cluster (Redis分佈式)

https://blog.csdn.net/chunlongyu/article/details/53339288

但從Redis 3.0開始,引入了Redis Cluster,今後Redis進入了真正的「分佈式集羣「時代。

 

image.png

P2P架構

 

image.png

爲何是16384?

很顯然,咱們須要維護節點和槽之間的映射關係,每一個節點須要知道本身有哪些槽,而且須要在結點之間傳遞這個消息。

爲了節省存儲空間,每一個節點用一個Bitmap來存放其對應的槽: 2k = 2*1024 *8 = 16384,也就是說,每一個結點用2k的內存空間,總共16384個比特位,就能夠存儲該結點對應了哪些槽。而後這2k的信息,經過Gossip協議,在結點之間傳遞。

客戶端存儲路由信息

對於客戶端來講,維護了一個路由表:每一個槽在哪臺機器上。這樣存儲(key, value)時,根據key計算出槽,再根據槽找到機器。

無損擴容

雖然Hash環(Memcached)能夠減小擴容時失效的key的數量,但畢竟有丟失。而在redis-cluster中,當新增機器以後,槽會在機器之間從新分配,同時被影響的數據會自動遷移,從而作到無損擴容。

這裏能夠結合補充知識點-緩存-一致性哈希來一塊兒理解。虛擬槽改變的是槽的分配,一致性哈希則會使旁邊的節點key失效。

主從複製

redis-cluster也引入了master-slave機制,從而提供了fail-over機制,這很大程度上解決了「緩存雪崩「的問題。關於這個,後面有機會再詳細闡述。

https://blog.csdn.net/men_wen/article/details/72853078

 

image.png

Redis集羣相對單機在功能上有必定限制。

key批量操做支持有限。如:MSET``MGET,目前只支持具備相同slot值的key執行批量操做

key事務操做支持有限。支持多key在同一節點上的事務操做,不支持分佈在多個節點的事務功能。

key做爲數據分區的最小粒度,所以不能將一個大的鍵值對象映射到不一樣的節點。如:hash、list。

不支持多數據庫空間。單機下Redis支持16個數據庫,集羣模式下只能使用一個數據庫空間,即db 0。

複製結構只支持一層,不支持嵌套樹狀複製結構。

十4、一個簡單的論壇系統分析

該論壇系統功能以下:

  • 能夠發佈文章;

  • 能夠對文章進行點贊;

  • 在首頁能夠按文章的發佈時間或者文章的點贊數進行排序顯示。

文章信息

文章包括標題、做者、贊數等信息,在關係型數據庫中很容易構建一張表來存儲這些信息,在 Redis 中可使用 HASH 來存儲每種信息以及其對應的值的映射。

Redis 沒有關係型數據庫中的表這一律念來將同種類型的數據存放在一塊兒,而是使用命名空間的方式來實現這一功能。鍵名的前面部分存儲命名空間,後面部分的內容存儲 ID,一般使用 : 來進行分隔。例以下面的 HASH 的鍵名爲 article:92617,其中 article 爲命名空間,ID 爲 92617。

 

image.png

點贊功能

當有用戶爲一篇文章點贊時,除了要對該文章的 votes 字段進行加 1 操做,還必須記錄該用戶已經對該文章進行了點贊,防止用戶點贊次數超過 1。能夠創建文章的已投票用戶集合來進行記錄。

爲了節約內存,規定一篇文章發佈滿一週以後,就不能再對它進行投票,而文章的已投票集合也會被刪除,能夠爲文章的已投票集合設置一個一週的過時時間就能實現這個規定。

 

image.png

對文章進行排序

爲了按發佈時間和點贊數進行排序,能夠創建一個文章發佈時間的有序集合和一個文章點贊數的有序集合。(下圖中的 score 就是這裏所說的點贊數;下面所示的有序集合分值並不直接是時間和點贊數,而是根據時間和點贊數間接計算出來的)

 

image.png

Redis經典面試題

Redis有哪些數據結構?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

若是你是Redis中高級用戶,還須要加上下面幾種數據結構HyperLogLog、Geo、Pub/Sub。

若是你說還玩過Redis Module,像BloomFilter,RedisSearch,Redis-ML,面試官得眼睛就開始發亮了。

使用過Redis分佈式鎖麼,它是什麼回事?

先拿setnx來爭搶鎖,搶到以後,再用expire給鎖加一個過時時間防止鎖忘記了釋放。

這時候對方會告訴你說你回答得不錯,而後接着問若是在setnx以後執行expire以前進程意外crash或者要重啓維護了,那會怎麼樣?

這時候你要給予驚訝的反饋:唉,是喔,這個鎖就永遠得不到釋放了。緊接着你須要抓一抓本身得腦殼,故做思考片刻,好像接下來的結果是你主動思考出來的,而後回答:我記得set指令有很是複雜的參數,這個應該是能夠同時把setnx和expire合成一條指令來用的。對方這時會顯露笑容,內心開始默唸:摁,這小子還不錯。

假如Redis裏面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,若是將它們所有找出來?

使用keys指令能夠掃出指定模式的key列表。

對方接着追問:若是這個redis正在給線上的業務提供服務,那使用keys指令會有什麼問題?

這個時候你要回答redis關鍵的一個特性:redis的單線程的。keys指令會致使線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可使用scan指令,scan指令能夠無阻塞的提取出指定模式的key列表,可是會有必定的重複機率,在客戶端作一次去重就能夠了,可是總體所花費的時間會比直接用keys指令長。

使用過Redis作異步隊列麼,你是怎麼用的?

通常使用list結構做爲隊列,rpush生產消息,lpop消費消息。當lpop沒有消息的時候,要適當sleep一會再重試。

若是對方追問可不能夠不用sleep呢?list還有個指令叫blpop,在沒有消息的時候,它會阻塞住直到消息到來。

若是對方追問能不能生產一次消費屢次呢?使用pub/sub主題訂閱者模式,能夠實現1:N的消息隊列。

若是對方追問pub/sub有什麼缺點?

在消費者下線的狀況下,生產的消息會丟失,得使用專業的消息隊列如rabbitmq等。

若是對方追問redis如何實現延時隊列?

使用sortedset,拿時間戳做爲score,消息內容做爲key調用zadd來生產消息,消費者用zrangebyscore指令獲取N秒以前的數據輪詢進行處理。

若是有大量的key須要設置同一時間過時,通常須要注意什麼?

若是大量的key過時時間設置的過於集中,到過時的那個時間點,redis可能會出現短暫的卡頓現象。通常須要在時間上加一個隨機值,使得過時時間分散一些。

Redis如何作持久化的?

bgsave作鏡像全量持久化,aof作增量持久化。

由於bgsave會耗費較長時間,不夠實時,在停機的時候會致使大量丟失數據,因此須要aof來配合使用。在redis實例重啓時,會使用bgsave持久化文件從新構建內存,再使用aof重放近期的操做指令來實現完整恢復重啓以前的狀態。

對方追問那若是忽然機器掉電會怎樣?取決於aof日誌sync屬性的配置,若是不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失數據。可是在高性能的要求下每次都sync是不現實的,通常都使用定時sync,好比1s1次,這個時候最多就會丟失1s的數據。

對方追問bgsave的原理是什麼?

你給出兩個詞彙就能夠了,fork和cow。fork是指redis經過建立子進程來進行bgsave操做,cow指的是copy on write,子進程建立後,父子進程共享數據段,父進程繼續提供讀寫服務,寫髒的頁面數據會逐漸和子進程分離開來。

Pipeline有什麼好處,爲何要用pipeline?

能夠將屢次IO往返的時間縮減爲一次,前提是pipeline執行的指令之間沒有因果相關性。使用redis-benchmark進行壓測的時候能夠發現影響redis的QPS峯值的一個重要因素是pipeline批次指令的數目。

Redis的同步機制瞭解麼?

Redis可使用主從同步,從從同步。第一次同步時,主節點作一次bgsave,並同時將後續修改操做記錄到內存buffer,待完成後將rdb文件全量同步到複製節點,複製節點接受完成後將rdb鏡像加載到內存。加載完成後,再通知主節點將期間修改的操做記錄同步到複製節點進行重放就完成了同步過程。

是否使用過Redis集羣,集羣的原理是什麼?

Redis Sentinal着眼於高可用,在master宕機時會自動將slave提高爲master,繼續提供服務。

Redis Cluster着眼於擴展性,在單個redis內存不足時,使用Cluster進行分片存儲。

參考與拓展閱讀

相關文章
相關標籤/搜索