Redis

2023-01-31 12:00:24

簡介

Redis 的全稱是 Remote Dictionary Server,是一個使用 C 語言編寫的、開源的(BSD 許可)高效能非關係型(NoSQL)的鍵值對資料庫。

Redis 的資料是儲存在記憶體中的,所以讀寫速度非常快,被廣泛應用於快取方向,當然也有持久化資料庫的用法。

優缺點

優點

  • 讀寫效能優異, Redis 能讀的速度是 110000 次/s,寫的速度是 81000 次/s
  • 資料型別豐富,有 String、List、Hash、Set、SortedSet 等
  • 單執行緒原子性,Redis 所有的操作都是原子性的,也支援多個操作合併後的原子執行
  • 豐富的特性,Redis 支援釋出訂閱、通知、key 過期等功能
  • 支援持久化,Redis 支援 RDB、AOF 等持久化方式
  • 高可用性,Redis 支援主從複製、哨兵模式、Cluster 等高可用方式

缺點

  • 資料庫容量受實體記憶體的限制,不能用作海量資料的讀寫
  • Redis 難以支援線上擴容,修改組態檔之後重啟 Redis,恢復磁碟上的資料耗費時間較久

使用場景

資料快取

Redis 是高效能的記憶體資料庫,因此,快取是 Redis 最常用的場景。Redis 作為快取使用的時候,一般是採用先更新資料庫,再刪除快取的 Cache Aside Pattern 策略。

整體的邏輯是:業務從 Redis 中讀取資料,如果 Redis 中存取不到資料,然後讀取資料庫中的資料,將資料庫中的資料快取到 Redis 中;業務更新資料,直接修改資料庫中的資料,然後將 Redis 中的快取刪除。

這種方案需要注意的就是:避免快取擊穿,資料的實時性相對較低。

限時業務

Redis 支援給 key 設定過期時間,使用者端無法存取到過期的 key,利用這一特性可以運用在限時的優惠活動、手機驗證碼等業務場景。

計數器

Redis 的 INCRBY 命令可以實現原子性的遞增,可以直接作為計數器儲存和遞增。尤其是,該命令在鍵不存在時會直接初始化值再執行 INCRBY 命令,執行成功後會直接返回計算增量之後的數值。

高並行的秒殺活動、分散式序列號的生成、限制手機傳送簡訊次數、介面限制存取次數等需要計數的功能,都涉及到計數器的概念,尤其是分散式系統會涉及到分散式計數器。

分散式鎖

先使用 SETNX 命令來爭搶鎖,結果返回 1 表示設定成功,搶到之後再用 EXPIRE 命令給鎖加一個過期時間防止忘記釋放鎖。

從 Redis 的 2.6.12 版本開始,可以通過 SET 命令的複雜引數,將 SETNX 命令和 EXPIRE 命令合併成一條命令來使用:

  • EX second: 設定鍵的過期時間為 second 秒,使用 EX 選項效果等同於 SETEX 命令。
  • PX millisecond: 設定鍵的過期時間為 millisecond 毫秒,使用 PX 選項效果等同於 PSETEX 命令。
  • NX: 只在鍵不存在時,才對鍵進行設定操作,使用 NX 選項效果等同於 SETNX 命令。
  • XX: 只在鍵已經存在時,才對鍵進行設定操作。

同樣的,使用 SET 命令操作成功之後會返回 OK,這樣才表示搶到了鎖。

為了避免分散式鎖被誤刪,加鎖時可以設定執行緒 ID 作為 value 值,刪除時需要執行緒的執行緒 ID 和 Redis 儲存的值一致才能夠刪除分散式鎖,否則只能等待鎖自動過期,整個刪除過程使用事務的方式保證原子性。

排行榜功能

通過 Redis 的 SortedSet 可以實現排行榜功能。

比如說需要展示點贊排行榜,可以將使用者 ID 作為 SortedSet 的 value 值,將使用者的點贊數作為 SortedSet 的 score 值,SortedSet 提供的 ZRANGEBYSCORE 命令可以快速返回已排序的點贊排行榜。

延時佇列

延時佇列其實就是一個帶有延遲功能的訊息佇列,Redis 可以通過 SortedSet 實現延時佇列。

具體的實現如下:

  1. 將訊息內容序列化成一個字串作為 SortedSet 的 value 值,這個訊息的到期處理時間作為 score,生產者呼叫 ZADD 命令生產訊息,消費者可以使用 ZRANGEBYSCORE 命令獲取一段時間之前的資料輪詢處理;
  2. 通常使用多個執行緒輪詢 SortedSet 獲取到期的任務進行處理,多個執行緒是為了保障可用性,萬一掛了一個執行緒還有其他執行緒可以繼續處理;
  3. 因為有多個執行緒,所以需要考慮並行爭搶任務,確保任務不能被多次執行,Redis 的 ZREM 命令是多執行緒爭搶任務的關鍵,ZREM 命令會返回被成功移除的成員數量,可以通過 ZREM 命令來決定任務的唯一屬主;
  4. 同時也要注意一定要進行異常捕獲,避免因為個別任務處理問題導致迴圈異常退出,同一個任務可能會被多個程序取到之後再使用 ZREM 命令進行爭搶,那些沒搶到的程序都是白取了一次任務,這是浪費;
  5. 通常可以使用 Lua 指令碼的方式,將 ZRANGEBYSCORE 命令和 ZREM 命令一同挪到伺服器端進行原子化操作,這樣多個程序之間爭搶任務時就不會出現這種浪費了。

非同步佇列

第一種方案 是使用 List 結構作為非同步佇列,RPUSH 命令生產訊息,LPOP 命令消費訊息。當使用 LPOP 命令沒有得到訊息的時候,需要適當 sleep 一會再重試,在這裡 sleep 會導致訊息的處理延遲增加。

如果不做 sleep 重試,改進的 第二種方案 是,使用 LPOP 命令的阻塞版本 BLPOP 命令,在 List 佇列中沒有訊息的時候,它會阻塞直到訊息到來,一旦資料到來,則立刻醒過來,訊息的延遲幾乎為零。但是如果執行緒一直阻塞,Redis 的使用者端連線就成了閒置連線,閒置過久,伺服器一般會主動斷開連線,減少閒置資源佔用。這個時候 BLPOP 命令會丟擲異常來,程式碼中需要處理這樣的異常。

除了 List 佇列之外,第三種方案 是使用 Pub/Sub 訂閱者模式實現一對多的訊息佇列。但是 Redis 的釋出訂閱功能是無狀態的,對於釋出者來說,無法知道釋出的訊息是否被訂閱者接收到,在消費者下線的情況下,生產的訊息會丟失。