Redis應用場景
Redis作爲一個非關係型數據庫,除了在訪問速度上擁有顯著優勢外,其本身支持的多種數據類型也非常有用,能覆蓋系統開發中的很多應用場景。下面列舉的場景有的是從網上其他人的博客裏看到的,有的自己開發時嘗試過的一些解決方案後記錄下來的,希望能給以後的開發帶來啓發。
在說應用場景前先說一些是否覺得使用Redis的建議
使用建議
- Redis 速度快是建立在內存數據庫基礎上的,但是一臺服務器的內存要比磁盤金貴許多,所以在項目初期不要想什麼都往 Redis 裏放,這樣當數據量上來後很快內存就會不夠用,反而得不償失。合理的利用有限的內存,將讀(寫)頻繁的熱數據放在 Redis 中才能更好感受到它帶來的性能提升。
- Redis 雖然提供了
RDB
和AOF
兩種持久化方式,但是普遍還是認爲 Redis 的持久化並不是很靠譜。非常重要的數據不要依賴Redis來開發,或者最起碼不要只在Redis中持久化 - MySQL經過不斷優化性能已經非常好,所以MySQL提供的數據結構和訪問效率能滿足的需求的情況下不要引入Redis,多引入一個組件就多一個可能的故障節點,尤其在保持數據一致性的場景中數據(比如用戶餘額)應該只放在數據庫中,除非你知道怎麼解決考系統的分佈式事務。
緩存
作爲Key-Value
形態的內存數據庫,Redis 最先會被想到的應用場景便是作爲數據緩存。而使用 Redis 緩存數據非常簡單,只需要通過string
類型將序列化後的對象存起來即可,不過也有一些需要注意的地方:
- 必須保證不同對象的 key 不會重複,並且使 key 儘量短,一般使用類名(表名)加主鍵拼接而成。
- 選擇一個優秀的序列化方式也很重要,目的是提高序列化的效率和減少內存佔用。
-
緩存內容與數據庫的一致性,這裏一般有兩種做法:
- 只在數據庫查詢後將對象放入緩存,如果對象發生了修改或刪除操作,直接清除對應緩存(或設爲過期)。
- 在數據庫新增和查詢後將對象放入緩存,修改後更新緩存,刪除後清除對應緩存(或設爲過期)。
消息隊列
Redis 中list
的數據結構實現是雙向鏈表,所以可以非常便捷的應用於消息隊列(生產者 / 消費者模型)。消息的生產者只需要通過lpush
將消息放入 list,消費者便可以通過rpop
取出該消息,並且可以保證消息的有序性。如果需要實現帶有優先級的消息隊列也可以選擇sorted set
。而pub/sub
功能也可以用作發佈者 / 訂閱者模型的消息。無論使用何種方式,由於 Redis 擁有持久化功能,也不需要擔心由於服務器故障導致消息丟失的情況。
時間軸(Timeline)
list
作爲雙向鏈表,不光可以作爲隊列使用。如果將它用作棧便可以成爲一個公用的時間軸。當用戶發完微博後,都通過lpush
將它存放在一個 key 爲LATEST_WEIBO
的list
中,之後便可以通過lrange
取出當前最新的微博。
循環鏈表
list
還可以作爲循環鏈表使用 RPOPLPUSH source destination
命令 RPOPLPUSH
在一個原子時間內,執行以下兩個動作:
- 將列表
source
中的最後一個元素(尾元素)彈出,並返回給客戶端。 - 將
source
彈出的元素插入到列表destination
,作爲destination
列表的的頭元素。
如果 source
和 destination
相同,則列表中的表尾元素被移動到表頭,並返回該元素,可以把這種特殊情況視作列表的旋轉(rotation)操作。
排行榜
使用sorted set
和一個計算熱度的算法便可以輕鬆打造一個熱度排行榜,zrevrangebyscore
可以得到以分數倒序排列的序列,zrank
可以得到一個成員在該排行榜的位置(是分數正序排列時的位置,如果要獲取倒序排列時的位置需要用zcard
-zrank
)。
計數器
計數功能應該是最適合 Redis 的使用場景之一了,因爲它高頻率讀寫的特徵可以完全發揮 Redis 作爲內存數據庫的高效。在 Redis 的數據結構中,string
、hash
和sorted set
都提供了incr
方法用於原子性的自增操作,下面舉例說明一下它們各自的使用場景:
- 如果應用需要顯示每天的註冊用戶數,便可以使用
string
作爲計數器,設定一個名爲REGISTERED_COUNT_TODAY
的 key,並在初始化時給它設置一個到凌晨 0 點的過期時間,每當用戶註冊成功後便使用incr
命令使該 key 增長 1,同時當每天凌晨 0 點後,這個計數器都會因爲 key 過期使值清零。 - 每條微博都有點贊數、評論數、轉發數和瀏覽數四條屬性,這時用
hash
進行計數會更好,將該計數器的 key 設爲weibo:weibo_id
,hash
的 field 爲like_number
、comment_number
、forward_number
和view_number
,在對應操作後通過hincrby
使hash 中
的 field 自增。 - 如果應用有一個發帖排行榜的功能,便選擇
sorted set
吧,將集合的 key 設爲POST_RANK
。當用戶發帖後,使用zincrby
將該用戶 id 的 score 增長 1。sorted set
會重新進行排序,用戶所在排行榜的位置也就會得到實時的更新。
好友關係
這個場景最開始是是一篇介紹微博 Redis 應用的 PPT 中看到的,其中提到微博的 Redis 主要是用在在計數和好友關係兩方面上,當時對好友關係方面的用法不太瞭解,後來看到《Redis 設計與實現》中介紹到作者最開始去使用 Redis 便是希望能通過set
解決傳統數據庫無法快速計算集合中交集這個功能。後來聯想到微博當前的業務場景,確實能夠以這種方式實現,所以姑且猜測一下:
對於一個用戶 A,將它的關注和粉絲的用戶 id 都存放在兩個 set 中:
-
A:follow
:存放 A 所有關注的用戶 id -
A:follower
:存放 A 所有粉絲的用戶 id那麼通過
sinter
命令便可以根據A:follow
和A:follower
的交集得到與 A 互相關注的用戶。當 A 進入另一個用戶 B 的主頁後,A:follow
和B:follow
的交集便是 A 和 B 的共同專注,A:follow
和B:follower
的交集便是 A 關注的人也關注了 B。
分佈式鎖
在 Redis 2.6.12 版本開始,string
的set
命令增加了三個參數:
-
EX
:設置鍵的過期時間(單位爲秒) -
PX
:設置鍵的過期時間(單位爲毫秒) -
NX
|XX
:當設置爲NX
時,僅當 key 存在時才進行操作,設置爲XX
時,僅當 key 不存在纔會進行操作由於這個操作是原子性的,可以簡單地以此實現一個分佈式的鎖,例如:
set key "lock" EX 1 XX
如果這個操作返回false
,說明 key 的添加不成功,也就是當前有人在佔用這把鎖。而如果返回true
,則說明得了鎖,便可以繼續進行操作,並且在操作後通過del
命令釋放掉鎖。並且即使程序因爲某些原因並沒有釋放鎖,由於設置了過期時間,該鎖也會在 1 秒後自動釋放,不會影響到其他程序的運行。
倒排索引
倒排索引是構造搜索功能的最常見方式,在 Redis 中也可以通過set
進行建立倒排索引,這裏以簡單的拼音 + 前綴搜索城市功能舉例:
假設一個城市北京
,通過拼音詞庫將北京
轉爲beijing
,再通過前綴分詞將這兩個詞分爲若干個前綴索引,有:北
、北京
、b
、be
…beijin
和beijing
。將這些索引分別作爲set
的 key(例如:index:北
)並存儲北京
的 id,倒排索引便建立好了。接下來只需要在搜索時通過關鍵詞取出對應的set
並得到其中的 id 即可。
個人能力侷限目前只知道這些數據類型的應用場景,如果各位有其他場景的應用經驗歡迎交流補充,另外面試時被問到爲何使用Redis不要簡單的說因爲快, 如果在系統中只使用了緩存這一個應用場景那麼最起碼可以提供一些MySQL的QPS和Redis的QPS數據或者程序在Redis使用前後的平均響應時長來印證你的觀點。