Redis問題彙總

翻身碼農把歌唱發表於2018-07-25

1. 什麼是Redis

Redis是由義大利人Salvatore Sanfilippo(網名:antirez)開發的一款記憶體快取記憶體資料庫。Redis全稱為:Remote Dictionary Server(遠端資料服務),該軟體使用C語言編寫,Redis是一個key-value儲存系統,它支援豐富的資料型別,如:string、list、set、zset(sorted set)、hash。

2. Redis特點

  1. 以記憶體作為資料儲存介質,讀寫資料的效率極高,遠遠超過資料庫。以設定和獲取一個256位元組字串為例,它的讀取速度可高達110000次/s,寫速度高達81000次/s。
  2. 支援資料的持久化,可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次載入進行使用。
  3. 支援多種資料型別String、List、Hash、Set、zset
  4. 支援資料的備份,即master-slave模式的資料備份

3.Redis有哪些資料型別,以及每種資料型別使用的場景

Redis支援多種資料型別,有String、List、Hash、Set、zset

String(字串)

String型別是二進位制安全的,意思是Redis的String可以包含任何資料,比如圖片或者序列化的物件等。一個Redis中字串的value最多可以是512M。一般做一些複雜的計數功能的快取。

List(列表)

List是按照插入順序排序的字串連結串列。

從元素插入和刪除的效率視角來看,如果我們是在連結串列的兩頭插入或刪除元素,這將會是非常高效的操作,即使連結串列中已經儲存了百萬條記錄,該操作也可以在常量時間內完成。然而需要說明的是,如果元素插入或刪除操作是作用於連結串列中間,那將會是非常低效的 。

Redis連結串列經常會被用於訊息佇列的服務,以完成多程式之間的訊息交換。假設一個應用程式正在執行LPUSH操作向連結串列中新增新的元素,我們通常將這樣的程式稱之為"生產者(Producer)",而另外一個應用程式正在執行RPOP操作從連結串列中取出元素,我們稱這樣的程式為"消費者(Consumer)"。如果此時,消費者程式在取出訊息元素後立刻崩潰,由於該訊息已經被取出且沒有被正常處理,那麼我們就可以認為該訊息已經丟失,由此可能會導致業務資料丟失,或業務狀態的不一致等現象的發生。然而通過使用RPOPLPUSH命令,消費者程式在從主訊息佇列中取出訊息之後再將其插入到備份佇列中,直到消費者程式完成正常的處理邏輯後再將該訊息從備份佇列中刪除。同時我們還可以提供一個守護程式,當發現備份佇列中的訊息過期時,可以重新將其再放回到主訊息佇列中,以便其它的消費者程式繼續處理。

可以利用 lrange 命令,做基於 Redis 的分頁功能,效能極佳,使用者體驗好

Hash(字典)

Hash是一個健值對集合,是一個String型別的key與value的對映表,特別適合用於儲存物件。 可用於儲存、讀取、修改使用者屬性, Hash 結構可以使你像在資料庫中 Update 一個屬性一樣只修改某一項屬性值。

Set(集合)

Set 是一個集合,集合的概念就是一堆不重複值的組合。利用 Redis 提供的 Set 資料結構,可以儲存一些集合性的資料。 集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1) 。Redis 非常人性化的為集合提供了求交集、並集、差集等操作,那麼就可以非常方便的實現如共同關注、共同喜好、二度好友等功能 。也可以做全域性去重的功能。

zset(Sorted Set,有序集合)

和Sets相比,Sorted Sets是將 Set 中的元素增加了一個權重引數 score,使得集合中的元素能夠按 score 進行有序排列。可以做排行榜應用,取 TOP N 操作。Sorted Set 可以用來做延時任務。最後一個應用就是可以做範圍查詢。

4.為什麼要用Redis

效能和併發。

效能:將一些耗時比較久,且結果不經常變動的SQL,放到Redis中,這樣,請求直接從快取中讀取,使得能夠迅速響應。

併發:大併發的情況下,所有的請求直接訪問資料庫,資料庫會出現連線異常。這個時候,就需要使用Redis做一個緩衝操作,讓請求先訪問到redis,而不是直接訪問資料庫 。

5.Redis有什麼缺點

  1. 快取和資料庫雙寫一致性問題
  2. 快取擊穿問題
  3. 快取雪崩問題
  4. 快取的併發競爭問題

6.Redis為什麼這麼快

  1. 純記憶體操作
  2. 單執行緒模型,避免了頻繁的上下文切換
  3. 採用非阻塞I/O多路複用機制

什麼是非阻塞I/O呢?

阻塞與非阻塞可以簡單理解為呼叫一個IO操作能不能立即得到返回應答,如果不能立即獲得返回,需要等待,那就阻塞了;否則就可以理解為非阻塞。

什麼是I/O多路複用機制呢?

單個執行緒,通過記錄跟蹤每個I/O流(sock)的狀態,來同時管理多個I/O流 。

I/O多路複用的優勢並不是對於單個連線能處理的更快,而是在於可以在單個執行緒/程式中處理更多的連線。與多程式和多執行緒技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必建立程式/執行緒,也不必維護這些程式/執行緒,從而大大減小了系統的開銷。

7.Redis的過期策略

Redis採用的過期策略是:定期刪除+惰性刪除策略。

定期刪除,Redis 預設每隔100ms 檢查,是否有過期的 Key,有過期 Key 則刪除。

需要說明的是,Redis 不是每隔100ms 將所有的 Key 檢查一次,而是隨機抽取進行檢查(如果每隔 100ms,全部 Key 進行檢查,Redis 豈不是卡死)。

定期刪除可以通過:
第一、配置redis.conf 的hz選項,預設為10 (即1秒執行10次,100ms一次,值越大說明重新整理頻率越快,最Redis效能損耗也越大,建議不要超過100) 
第二、配置redis.conf的maxmemory最大值,當已用記憶體超過maxmemory限定時,就會觸發主動清理策略
複製程式碼

因此,如果只採用定期刪除策略,會導致很多 Key 到時間沒有刪除。於是,惰性刪除派上用場。

也就是說在你獲取某個 Key 的時候,Redis 會檢查一下,這個 Key 如果設定了過期時間,如果過期了,此時就會刪除。

過期策略可以參考:[Redis資料過期策略詳解](www.cnblogs.com/xuliangxing…)

8.Redis記憶體淘汰機制

在 redis.conf 中有一行配置

# maxmemory-policy volatile-lru
複製程式碼

Redis記憶體淘汰策略有(觸發該策略的機制是 當記憶體不足以容納新寫入資料時):

  • noeviction:誰也不刪,直接在寫操作時返回錯誤 。應該沒人用吧。
  • allkeys-lru:在鍵空間中,移除最近最少使用的 Key。推薦使用,目前專案在用這種。
  • allkeys-random:在鍵空間中,隨機移除某個 Key。應該也沒人用吧,你不刪最少使用 Key,去隨機刪。
  • volatile-lru:在設定了過期時間的鍵空間中,移除最近最少使用的 Key。這種情況一般是把 Redis 既當快取,又做持久化儲存的時候才用。不推薦。
  • volatile-random:在設定了過期時間的鍵空間中,隨機移除某個 Key。依然不推薦。
  • volatile-ttl:在設定了過期時間的鍵空間中,有更早過期時間的 Key 優先移除。不推薦。

9.Redis和資料庫雙寫一致性問題

首先,採取正確更新策略,先更新資料庫,再刪快取。其次,因為可能存在刪除快取失敗的問題,提供一個補償措施即可,例如利用訊息佇列。

10.如何應對快取穿透、快取雪崩、快取擊穿問題

快取穿透:黑客故意去請求快取中不存在的資料,導致所有的請求都懟到資料庫上,從而資料庫連線異常。

解決方法:

  1. 採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免了對底層資料庫的查詢壓力(推薦)
  2. 如果一個查詢返回的資料為空(不管是數 據不存在,還是系統故障),仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘(推薦,簡單暴力)
  3. 利用互斥鎖,快取失效的時候,先去獲得鎖,得到鎖了,再去請求資料庫。沒得到鎖,則休眠一段時間重試
  4. 採用非同步更新策略,無論 Key 是否取到值,都直接返回。Value 值中維護一個快取失效時間,快取如果過期,非同步起一個執行緒去讀資料庫,更新快取。需要做快取預熱(專案啟動前,先載入快取)操作。

快取雪崩:當快取伺服器重啟或者大量快取集中在某一個時間段失效,來了一批請求,請求全部到DB,DB瞬時壓力過重雪崩。

解決方法:

  1. 給快取的失效時間加上一個隨機值,避免集體失效
  2. 使用互斥鎖,但是該方案吞吐量明顯下降了
  3. 雙快取,我們有兩個快取,快取 A 和快取 B。快取 A 的失效時間為 20 分鐘,快取 B 不設失效時間。自己做快取預熱操作。然後細分以下幾個小點:從快取 A 讀資料庫,有則直接返回;A 沒有資料,直接從 B 讀資料,直接返回,並且非同步啟動一個更新執行緒,更新執行緒同時更新快取 A 和快取 B。(推薦)

快取擊穿:對於一些設定了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的資料。這個時候,需要考慮一個問題:快取被“擊穿”的問題,這個和快取雪崩的區別在於這裡針對某一key快取,前者則是很多key。 快取在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現快取過期一般都會從後端DB載入資料並回設到快取,這個時候大併發的請求可能會瞬間把後端DB壓垮。

解決方法:

  1. 使用互斥鎖
  2. "提前"使用互斥鎖
  3. "永不過期"

參考:

快取穿透,快取擊穿,快取雪崩解決方案分析

Redis架構之防雪崩設計:網站不當機背後的兵法

11.若Redis中有1億個key,其中有10w是以某個固定的已知字首開頭,怎麼將它們全部找出來

使用keys指令可以掃出指定模式的key列表。如果這個redis正在給線上的業務提供服務,keys指令會導致執行緒阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重複概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。

12.怎麼使用Redis做非同步佇列

一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息。當lpop沒有訊息的時候,要適當sleep一會再重試。 如果不用sleep,list還有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來。

使用pub/sub主題訂閱者模式,可以實現1:N的訊息佇列。 也就是生產一次消費多次。但是使用pub/sub是有缺點的,在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如rabbitmq等。

redis如何實現延時佇列:使用sortedset,拿時間戳作為score,訊息內容作為key,呼叫zadd來生產訊息,消費者用zrangebyscore指令獲取N秒之前的資料輪詢進行處理。

13.怎麼用Redis實現分散式鎖

主要是使用了redis 的setnx命令,快取了鎖,reids快取的key是鎖的key,所有的共享, value是鎖的到期時間(注意:這裡把過期時間放在value了,沒有時間上設定其超時時間)。

1.通過setnx嘗試設定某個key的值,成功(當前沒有這個鎖)則返回,成功獲得鎖

2.鎖已經存在則獲取鎖的到期時間,和當前時間比較,超時的話,則設定新的值

實現方法可以參考:Redis分散式鎖實現

14.參考

為什麼分散式一定要有Redis?

天下無難試之Redis面試刁難大全

歡迎關注我的公眾號哦~ 搜尋公眾號:翻身碼農把歌唱 或者 掃描下方二維碼:

img

相關文章