天天看點

Redis常用場景、資料結構、讀寫一緻、緩存穿透、緩存雪崩等

一、分布式系統為什麼要用Redis

1、性能

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

2、并發

在大并發的情況下,所有的請求直接通路資料庫,資料庫會出現連接配接異常。這個時候,就需要使用 Redis 做一個緩沖操作,讓請求先通路到 Redis,而不是直接通路資料庫。

二、應用場景

1、緩存

緩存現在幾乎是所有中大型網站都在用的必殺技,合理的利用緩存不僅能夠提升網站通路速度,還能大大降低資料庫的壓力。Redis提供了鍵過期功能,也提供了靈活的鍵淘汰政策,是以,現在Redis用在緩存的場合非常多。

2、排行榜

很多網站都有排行榜應用的,如京東的月度銷量榜單、商品按時間的上新排行榜等。Redis提供的有序集合資料類構能實作各種複雜的排行榜應用。

3、計數器

什麼是計數器,如電商網站商品的浏覽量、視訊網站視訊的播放數等。為了保證資料實時效,每次浏覽都得給+1,并發量高時如果每次都請求資料庫操作無疑是種挑戰和壓力。Redis提供的incr指令來實作計數器功能,記憶體操作,性能非常好,非常适用于這些計數場景。

4、分布式會話

叢集模式下,在應用不多的情況下一般使用容器自帶的session複制功能就能滿足,當應用增多相對複雜的系統中,一般都會搭建以Redis等記憶體資料庫為中心的session服務,session不再由容器管理,而是由session服務及記憶體資料庫管理。

5、分布式鎖

在很多網際網路公司中都使用了分布式技術,分布式技術帶來的技術挑戰是對同一個資源的并發通路,如全局ID、減庫存、秒殺等場景,并發量不大的場景可以使用資料庫的悲觀鎖、樂觀鎖來實作,但在并發量高的場合中,利用資料庫鎖來控制資源的并發通路是不太理想的,大大影響了資料庫的性能。可以利用Redis的setnx功能來編寫分布式的鎖,如果設定傳回1說明擷取鎖成功,否則擷取鎖失敗,實際應用中要考慮的細節要更多。

6、社交網絡

點贊、踩、關注/被關注、共同好友等是社交網站的基本功能,社交網站的通路量通常來說比較大,而且傳統的關系資料庫類型不适合存儲這種類型的資料,Redis提供的哈希、集合等資料結構能很友善的的實作這些功能。

7、最新清單

Redis清單結構,LPUSH可以在清單頭部插入一個内容ID作為關鍵字,LTRIM可用來限制清單的數量,這樣清單永遠為N個ID,無需查詢最新的清單,直接根據ID去到對應的内容頁即可。

8、消息系統

消息隊列是大型網站必用中間件,如ActiveMQ、RabbitMQ、Kafka等流行的消息隊列中間件,主要用于業務解耦、流量削峰及異步處理實時性低的業務。Redis提供了釋出/訂閱及阻塞隊列功能,能實作一個簡單的消息隊列系統。另外,這個不能和專業的消息中間件相比。

三、Redis為什麼這麼快

主要是以下三點:

  • 純記憶體操作
  • 單線程工作模型,避免了頻繁的上下文切換
  • 采用了非阻塞 I/O 多路複用機制

四、Redis資料類型

1、String

這個沒啥好說的,最正常的 set/get 操作,Value 可以是 String 也可以是數字。一般做一些複雜的計數功能的緩存。

2、Hash

這裡 Value 存放的是結構化的對象,比較友善的就是操作其中的某個字段。

做單點登入的時候,可以用這種資料結構存儲使用者資訊,以 CookieId 作為 Key,設定 30 分鐘為緩存過期時間,能很好的模拟出類似 Session 的效果。

3、List

使用 List 的資料結構,可以做簡單的消息隊列的功能。另外還有一個就是,可以利用 lrange 指令,做基于 Redis 的分頁功能,性能極佳,使用者體驗好。

4、Set

因為 Set 堆放的是一堆不重複值的集合。是以可以做全局去重的功能。為什麼不用 JVM 自帶的 Set 進行去重?因為我們的系統一般都是叢集部署,使用 JVM 自帶的 Set,比較麻煩,難道為了一個做一個全局去重,再起一個公共服務,太麻煩了。

另外,就是利用交集、并集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。

5、Sorted Set

Sorted Set多了一個權重參數 Score,集合中的元素能夠按 Score 進行排列。可以做排行榜應用,取 TOP N 操作。Sorted Set 可以用來做延時任務。最後一個應用就是可以做範圍查找。點選這裡檢視Redis面試題彙總。

五、Redis 的過期政策以及記憶體淘汰機制

比如你 Redis 隻能存 5G 資料,可是你寫了 10G,那會删 5G 的資料。怎麼删的,這個問題思考過麼?

還有,你的資料已經設定了過期時間,但是時間到了,記憶體占用率還是比較高,有思考過原因麼?

回答:Redis 采用的是定期删除+惰性删除政策。

1、定期删除+惰性删除是如何工作

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

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

是以,如果隻采用定期删除政策,會導緻很多 Key 到時間沒有删除。于是,惰性删除派上用場。

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

2、采用定期删除+惰性删除就沒其他問題了麼?

不是的,如果定期删除沒删除 Key。然後你也沒即時去請求 Key,也就是說惰性删除也沒生效。這樣,Redis的記憶體會越來越高。那麼就應該采用記憶體淘汰機制。

3、記憶體淘汰機制

在 redis.conf 中有一行配置:

# maxmemory-policy volatile-lru
           

該配置就是配記憶體淘汰政策的:

  • noeviction

    :當記憶體不足以容納新寫入資料時,新寫入操作會報錯。應該沒人用吧。
  • allkeys-lru

    :當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 Key。推薦使用,目前項目在用這種。
  • allkeys-random

    :當記憶體不足以容納新寫入資料時,在鍵空間中,随機移除某個 Key。不推薦。
  • volatile-lru

    :當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的 Key。這種情況一般是把 Redis 既當緩存,又做持久化存儲的時候才用。不推薦。
  • volatile-random

    :當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,随機移除某個 Key。依然不推薦。
  • volatile-ttl

    :當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的 Key 優先移除。不推薦。
如果沒有設定 expire 的 Key,那麼 volatile-lru,volatile-random 和 volatile-ttl 政策的行為,和 noeviction(不删除) 基本上一緻。

六、Redis常見問題

1、Redis 和 DB 一緻性

查詢:

先查Redis,沒命中再查DB,然後寫Redis

更新:

先删Redis,再更新DB,然後寫Redis

2、緩存穿透

概念:

一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,就去DB查找。如果key對應的value是一定不存在的,并且對該key并發請求量很大,就會對DB造成很大的壓力。這就叫做緩存穿透。

解決方案:

  • 對查詢結果為空的情況也進行緩存,緩存時間設定短一點,或者該key對應的資料insert了之後清理緩存;
  • 對一定不存在的key進行過濾。可以把所有的可能存在的key放到一個大的set中,查詢時通過該set過濾(用的較少);

3、緩存雪崩

含義:

當緩存伺服器重新開機或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給DB帶來很大壓力。

解決方案

  • 在緩存失效後,通過加鎖或者隊列來控制讀資料庫寫緩存的線程數量。比如對某個key隻允許一個線程查詢資料和寫緩存,其他線程等待;
  • 不同的key,設定不同的過期時間,讓緩存失效的時間點盡量均勻;
  • 做二級緩存,A1為原始緩存,A2為拷貝緩存,A1失效時,可以通路A2,A1緩存失效時間設定為短期,A2設定為長期;

4、并發競争Key

這個問題大緻就是,同時有多個子系統去 Set 一個 Key。這個時候大家思考過要注意什麼呢?

如果對這個 Key 操作,不要求順序:

這種情況下,準備一個分布式鎖,大家去搶鎖,搶到鎖就做 set 操作即可,比較簡單。

如果對這個 Key 操作,要求順序:

假設有一個 key1,系統 A 需要将 key1 設定為 valueA,系統 B 需要将 key1 設定為 valueB,系統 C 需要将 key1 設定為 valueC。

期望按照 key1 的 value 值按照

valueA > valueB > valueC

的順序變化。這種時候我們在資料寫入資料庫的時候,需要儲存一個時間戳。

假設時間戳如下:

系統A key 1 {valueA  3:00}
    系統B key 1 {valueB  3:05}
    系統C key 1 {valueC  3:10}
           

那麼,假設這會系統 B 先搶到鎖,将 key1 設定為

{valueB 3:05}

。接下來系統 A 搶到鎖,發現自己的 valueA 的時間戳早于緩存中的時間戳,那就不做 set 操作了,以此類推。

其他方法,比如利用隊列,将 set 方法變成串行通路也可以。總之,靈活變通。