簡介
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 實現延時佇列。
具體的實現如下:
- 將訊息內容序列化成一個字串作為 SortedSet 的 value 值,這個訊息的到期處理時間作為 score,生產者呼叫
ZADD
命令生產訊息,消費者可以使用ZRANGEBYSCORE
命令獲取一段時間之前的資料輪詢處理; - 通常使用多個執行緒輪詢 SortedSet 獲取到期的任務進行處理,多個執行緒是為了保障可用性,萬一掛了一個執行緒還有其他執行緒可以繼續處理;
- 因為有多個執行緒,所以需要考慮併發爭搶任務,確保任務不能被多次執行,Redis 的
ZREM
命令是多執行緒爭搶任務的關鍵,ZREM
命令會返回被成功移除的成員數量,可以透過ZREM
命令來決定任務的唯一屬主; - 同時也要注意一定要進行異常捕獲,避免因為個別任務處理問題導致迴圈異常退出,同一個任務可能會被多個程式取到之後再使用
ZREM
命令進行爭搶,那些沒搶到的程式都是白取了一次任務,這是浪費; - 通常可以使用 Lua 指令碼的方式,將
ZRANGEBYSCORE
命令和ZREM
命令一同挪到伺服器端進行原子化操作,這樣多個程式之間爭搶任務時就不會出現這種浪費了。
非同步佇列
第一種方案 是使用 List 結構作為非同步佇列,RPUSH
命令生產訊息,LPOP
命令消費訊息。當使用 LPOP
命令沒有得到訊息的時候,需要適當 sleep 一會再重試,在這裡 sleep 會導致訊息的處理延遲增加。
如果不做 sleep 重試,改進的 第二種方案 是,使用 LPOP
命令的阻塞版本 BLPOP
命令,在 List 佇列中沒有訊息的時候,它會阻塞直到訊息到來,一旦資料到來,則立刻醒過來,訊息的延遲幾乎為零。但是如果執行緒一直阻塞,Redis 的客戶端連線就成了閒置連線,閒置過久,伺服器一般會主動斷開連線,減少閒置資源佔用。這個時候 BLPOP
命令會丟擲異常來,程式碼中需要處理這樣的異常。
除了 List 佇列之外,第三種方案 是使用 Pub/Sub 訂閱者模式實現一對多的訊息佇列。但是 Redis 的釋出訂閱功能是無狀態的,對於釋出者來說,無法知道釋出的訊息是否被訂閱者接收到,在消費者下線的情況下,生產的訊息會丟失。