Redis常見面試題(一)

1、爲什麼使用redis?

主要有兩個考慮角度:高性能、高併發。

(PS:Redis大多數情況下用在緩存上,或者共享Session上面。如果只是爲了分佈式鎖這些其他功能,還有其他中間件 Zookpeer 等代替,並非一定要使用 Redis。)

我們在碰到需要執行耗時特別久,且結果不頻繁變動的 SQL,就特別適合將運行結果放入緩存。這樣,後面的請求就去緩存中讀取,使得請求能夠迅速響應。

例如:現在有一個商品秒殺系統,同一時間,有幾萬甚至幾十萬的人在刷新頁面數據。執行的是同一操作——向數據庫查數據,查詢的是同一個商品的信息,一次查詢的時間是600ms,這樣對於我們的數據庫而言對於幾萬幾十萬的併發是無法支撐的,因爲我們數據庫一般單機情況2000qps就差不多報警了,即使能支撐,也需要太多從機數據庫,而且執行的都是重複的命令,這對於數據庫未免太浪費性能了。這時我們把緩存(redis或memcached)加進來,先去緩存中查詢,如果緩存中沒有,再去數據庫查,把數據庫中查出來的結果放到redis中,下次再有人查,別走 mysql 折騰 600ms 了,直接從緩存裏查詢,2ms 搞定。性能提升 300 倍,而且可以省去一大堆對數據庫的請求,減少了數據庫的壓力。

所以,通過上面的例子我們可以看出來,使用緩存可以提高性能(600ms變成了2ms),可以支撐更高的併發(減少了數據庫的壓力)。

但是:既然memcached也能實現,爲什麼我們非要用redis呢?繼續往下看。

 

2、redis和memcached有什麼區別?

  • redis 支持複雜的數據結構

redis 相比 memcached 來說,擁有更多的數據結構,能支持更豐富的數據操作。如果需要緩存能夠支持更復雜的結構和操作, redis 會是不錯的選擇(這是redis最大的優點之一,也是大多企業選擇redis的原因之一)。

  • redis 原生支持集羣模式

在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 沒有原生的集羣模式,需要依靠客戶端來實現往集羣中分片寫入數據。

  • 性能對比

由於 redis 只使用單核,而 memcached 可以使用多核,所以平均每一個核上 redis 在存儲小數據時比 memcached 性能更高。而在 100k 以上的數據中,memcached 性能要高於 redis。雖然 redis 最近也在存儲大數據的性能上進行優化,但是比起 memcached,還是稍有遜色。

  • redis 的線程模型

redis 內部使用文件事件處理器 file event handler(具體:請看本博客最下方的補充),這個文件事件處理器是單線程的,所以 redis 才叫做單線程的模型。它採用 IO 多路複用機制同時監聽多個 socket,將 socket 產生的事件壓入內存隊列中,事件分派器根據 socket 上的事件來選擇對應的事件處理器進行處理。

 

總結:大多數企業之所以選擇Redis而不選擇memcached都是因爲前兩個原因,即便有第三個缺點的存在,但是對於大多數企業的需求,影響並不大,因此近些年很多企業都開始由memcached轉到redis上面來。

 

3、爲什麼redis單線程模型效率這麼高?

  • 純內存操作
  • 核心是基於非阻塞的 IO 多路複用機制
  • 單線程反而避免了多線程的頻繁上下文切換問題

 

4、redis都有哪些數據類型?

  • string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
#string(字符串,使用方式爲get和set,用做最基礎key/value緩存。)
set key1 value1
get key1


#hash(可以理解成map)
hset myhash name mercury    #往myhash中set一個鍵值對
hset myhash age 26          #往person中set一個鍵值對 
hset myhash id 1            #往myhash中set一個鍵值對
hget myhash name            #讀取myhash中的某個字段的值
"mercury"
#myhash的結構如下
#myhash = {
#    "name": "mercury",
#    "age": 26,
#    "id": 1
#}


#list(有序列表)
lpush mylist 1        #往mylist中push一個值
lpush mylist 2        #往mylist中push一個值
lpush mylist 3 4 5    #往mylist中push多個值
rpop mylist           #以先進後出的方式,從mylist中取值。
#可以使用lrange實現分頁功能,性能高。
#0開始位置,-1結束位置,結束位置爲-1時,表示列表的最後一個位置,即查看所有。
lrange mylist 0 -1


#set(無序集合,自動去重。)
sadd mySet 1            #添加元素
smembers mySet          #查看全部元素
sismember mySet 3       #判斷是否包含某個值
srem mySet 1            #刪除某個/些元素
srem mySet 2 4          #刪除某個/些元素
scard mySet             #查看元素個數
spop mySet              #隨機刪除一個元素
smove testSet mySet 2   #將一個set的元素移動到另外一個set
sinter testSet mySet    #求兩set的交集
sunion testSet mySet    #求兩set的並集
sdiff yourSet mySet     #求在testSet中而不在mySet中的元素


#zset(zset是排序的set)
#注意:不同的是每個元素都會關聯一個double類型的分數。
#redis正是通過分數來爲集合中的成員進行從小到大的排序。
zset的成員是唯一的,但分數(score)卻可以重複。
zadd sun 80 venus       #往sun中添加元素
zadd sun 75 earth       #往sun中添加元素
zadd sun 90 mercury     #往sun中添加元素
zadd sun 62 mars        #往sun中添加元素

zrevrange sun 0 3      # 獲取排名前三的用戶(默認是升序,所以需要 rev 改爲降序)
zrank sun earth        #獲取某用戶的排名

 

5、redis有哪些過期策略?

兩種:定期刪除和惰性刪除。

  • 定期刪除:

Redis 默認每個 100ms 檢查,有過期 Key 則刪除。需要說明的是,Redis 不是每個 100ms 將所有的 Key 檢查一次,而是隨機抽取進行檢查,因爲如果redis中有數量很大的key(例如幾十萬key),那麼每隔100ms檢查一次,redis很可能就死掉了,因爲數據太多,檢查太頻繁,cpu負載太高了。如果只採用定期刪除策略,會導致很多 Key 到時間沒有刪除。於是,惰性刪除派上用場。

  • 過期刪除:

當你在獲取某個 key 的時候,redis 會檢查這個 key是否設置了過期時間,如果設置了過期時間,檢查key是否過期,如果過期了此時就會刪除,返回空值。

 

注意:由於定期刪除是隨機抽取 key進行檢查的,所以一定會漏掉一部分key,而這時候如果該key也沒走惰性刪除,那相當於沉積在內存中了,時間一久,必定大量堆積在內存中,導致內存耗盡。這時候要依靠redis的內存淘汰機制。繼續往下看。

 

6、redis有哪些內存淘汰機制?

  • noeviction: 當內存不足以容納新寫入數據時,新寫入操作會報錯(使用較少)。
  • allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的 key(這個是最常用)。
  • allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個 key(使用較少)。
  • volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的 key(使用較少)。
  • volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個 key(使用較少)。
  • volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的 key 優先移除(使用較少)。

配置內存淘汰機制方式:

在 redis.conf 中有一行配置:

# maxmemory-policy volatile-lru

 

7、redis主從架構?

  • 主從架構:

一般一臺redis平均能支撐的QPS爲幾萬。對於緩存而言,大多數情況下是用來支撐讀高併發的。所以會把架構做成主從(master-slave)架構,一主多從,主(master)負責寫請求,並將數據複製到其它的從(slave)節點,從節點負責讀請求。這樣可以很輕鬆實現水平擴容,以支撐讀高併發。

 

  • 主從機制: 
  1. redis採用異步方式把數據複製到從(slave)節點,自redis2.8 開始,slave會週期性地確認自己每次複製的數據量;

  2. 一個主節點(master node)是可以配置多個從節點(slave node)的;

  3. 從節點可以連接其他的從節點;

  4. 從節點做複製的時候,不會阻塞主節點的正常工作;

  5. 從節點在做複製的時候,也不會阻塞對自己的查詢操作,它會用舊的數據集來爲外部請求提供服務;但是複製完成的時候,會先刪除舊數據集,加載新數據集,這個時候會暫停對外服務;

  6. 從節點主要用來進行橫向擴容,做讀寫分離,擴容的從節點可以提高讀的吞吐量。

注意:在採用主從架構時,建議開啓主節點的持久化(主節點的各種備份方案也建議開啓),不建議用子節點作爲主節點的數據熱備份,因爲,如果把主節點的持久化功能關掉,可能在(主節點)master宕機重啓的時候數據是空的,這時從節點可能會來主節點複製數據,這樣從節點(slave node)的數據也丟了。

 

 


補充:文件事件處理器(File Event Handler

IO多路複用:I/O是指網絡I/O,多路指多個TCP連接(即socket或者channel),複用指複用一個或幾個線程。意思說一個或一組線程處理多個TCP連接。

例子:模擬一個tcp服務器處理30個客戶socket。

假設你是一個老師,讓30個學生解答一道題目,然後檢查學生做的是否正確,你有下面幾個選擇:   

1. 第一種選擇:按順序逐個檢查,先檢查A,然後是B,之後是C、D......這中間如果有一個學生卡住,全班都會被耽誤。這種模式就好比,你用循環挨個處理socket,根本不具有併發能力。 (單線程模型)  

2. 第二種選擇:你創建30個分身,每個分身檢查一個學生的答案是否正確。 這種類似於爲每一個用戶創建一個進程或者線程處理連接。 (多線程模型) 

3. 第三種選擇,你站在講臺上等,誰解答完誰舉手。這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然後繼續回到講臺上等。此時E、A又舉手,然後去處理E和A.... 。(IO多路複用模型)

文件事件處理器:

  如果是客戶端要連接redis,那麼會爲socket關聯連接應答處理器
  如果是客戶端要寫數據到redis,那麼會爲socket關聯命令請求處理器
  如果是客戶端要從redis讀數據,那麼會爲socket關聯命令回覆處理器

客戶端與redis通信的一次流程:

  在redis啓動初始化的時候,redis會將連接應答處理器跟AE_READABLE事件關聯起來,接着如果一個客戶端跟redis發起連接,此時會產生一個AE_READABLE事件,然後由連接應答處理器來處理跟客戶端建立連接,創建客戶端對應的socket,同時將這個socket的AE_READABLE事件跟命令請求處理器關聯起來。

  當客戶端向redis發起請求的時候(不管是讀請求還是寫請求,都一樣),首先就會在socket產生一個AE_READABLE事件,然後由對應的命令請求處理器來處理。這個命令請求處理器就會從socket中讀取請求相關數據,然後進行執行和處理。接着redis這邊準備好了給客戶端的響應數據之後,就會將socket的AE_WRITABLE事件跟命令回覆處理器關聯起來,當客戶端這邊準備好讀取響應數據時,就會在socket上產生一個AE_WRITABLE事件,會由對應的命令回覆處理器來處理,就是將準備好的響應數據寫入socket,供客戶端來讀取。

  命令回覆處理器寫完之後,就會刪除這個socket的AE_WRITABLE事件和命令回覆處理器的關聯關係。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章