Redis 每秒可以處理超過 10 萬次讀寫操作,是已知性能最快的 key-value 資料庫,稱得上是必須要學會的知識。 ...
簡介
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 的發佈訂閱功能是無狀態的,對於發佈者來說,無法知道發佈的消息是否被訂閱者接收到,在消費者下線的情況下,生產的消息會丟失。