Redis 應用場景彙總

KevinYan發表於2019-09-01

Redis作為一個非關係型資料庫,除了在訪問速度上擁有顯著優勢外,其本身支援的多種資料型別也非常有用,能覆蓋系統開發中的很多應用場景。下面列舉的場景有的是從網上其他人的部落格裡看到的,有的自己開發時嘗試過的一些解決方案後記錄下來的,希望能給以後的開發帶來啟發。

在說應用場景前先說一些是否覺得使用Redis的建議

使用建議

  • Redis 速度快是建立在記憶體資料庫基礎上的,但是一臺伺服器的記憶體要比磁碟金貴許多,所以在專案初期不要想什麼都往 Redis 裡放,這樣當資料量上來後很快記憶體就會不夠用,反而得不償失。合理的利用有限的記憶體,將讀(寫)頻繁的熱資料放在 Redis 中才能更好感受到它帶來的效能提升。
  • Redis 雖然提供了RDBAOF兩種持久化方式,但是普遍還是認為 Redis 的持久化並不是很靠譜。非常重要的資料不要依賴Redis來開發,或者最起碼不要只在Redis中持久化
  • MySQL經過不斷優化效能已經非常好,所以MySQL提供的資料結構和訪問效率能滿足的需求的情況下不要引入Redis,多引入一個元件就多一個可能的故障節點,尤其在保持資料一致性的場景中資料(比如使用者餘額)應該只放在資料庫中,除非你知道怎麼解決考系統的分散式事務。

快取

作為Key-Value形態的記憶體資料庫,Redis 最先會被想到的應用場景便是作為資料快取。而使用 Redis 快取資料非常簡單,只需要通過string型別將序列化後的物件存起來即可,不過也有一些需要注意的地方:

  • 必須保證不同物件的 key 不會重複,並且使 key 儘量短,一般使用類名(表名)加主鍵拼接而成。
  • 選擇一個優秀的序列化方式也很重要,目的是提高序列化的效率和減少記憶體佔用。
  • 快取內容與資料庫的一致性,這裡一般有兩種做法:
    1. 只在資料庫查詢後將物件放入快取,如果物件發生了修改或刪除操作,直接清除對應快取(或設為過期)。
    2. 在資料庫新增和查詢後將物件放入快取,修改後更新快取,刪除後清除對應快取(或設為過期)。

訊息佇列

Redis 中list的資料結構實現是雙向連結串列,所以可以非常便捷的應用於訊息佇列(生產者 / 消費者模型)。訊息的生產者只需要通過lpush將訊息放入 list,消費者便可以通過rpop取出該訊息,並且可以保證訊息的有序性。如果需要實現帶有優先順序的訊息佇列也可以選擇sorted set。而pub/sub功能也可以用作釋出者 / 訂閱者模型的訊息。無論使用何種方式,由於 Redis 擁有持久化功能,也不需要擔心由於伺服器故障導致訊息丟失的情況。

時間軸(Timeline)

list作為雙向連結串列,不光可以作為佇列使用。如果將它用作棧便可以成為一個公用的時間軸。當使用者發完微博後,都通過lpush將它存放在一個 key 為LATEST_WEIBOlist中,之後便可以通過lrange取出當前最新的微博。

迴圈連結串列

list 還可以作為迴圈連結串列使用 RPOPLPUSH source destination

命令 RPOPLPUSH 在一個原子時間內,執行以下兩個動作:

  • 將列表 source 中的最後一個元素(尾元素)彈出,並返回給客戶端。
  • source 彈出的元素插入到列表 destination ,作為 destination 列表的的頭元素。

如果 sourcedestination 相同,則列表中的表尾元素被移動到表頭,並返回該元素,可以把這種特殊情況視作列表的旋轉(rotation)操作。

比如有個程式來完成派單任務,需要將使用者傳送過來的申請依次派發給工作人員,那麼就可以把工作人員的身份標示維護在迴圈列表中,從列表尾部讀取每次讀取身份標示後相應的標示都會被放到列表頭如此迴圈往復。

排行榜

使用sorted set和一個計算熱度的演算法便可以輕鬆打造一個熱度排行榜,zrevrangebyscore可以得到以分數倒序排列的序列,zrank可以得到一個成員在該排行榜的位置(是分數正序排列時的位置,如果要獲取倒序排列時的位置需要用zcard-zrank)。

計數器

計數功能應該是最適合 Redis 的使用場景之一了,因為它高頻率讀寫的特徵可以完全發揮 Redis 作為記憶體資料庫的高效。在 Redis 的資料結構中,stringhashsorted set都提供了incr方法用於原子性的自增操作,下面舉例說明一下它們各自的使用場景:

  • 如果應用需要顯示每天的註冊使用者數,便可以使用string作為計數器,設定一個名為REGISTERED_COUNT_TODAY的 key,並在初始化時給它設定一個到凌晨 0 點的過期時間,每當使用者註冊成功後便使用incr命令使該 key 增長 1,同時當每天凌晨 0 點後,這個計數器都會因為 key 過期使值清零。
  • 每條微博都有點贊數、評論數、轉發數和瀏覽數四條屬性,這時用hash進行計數會更好,將該計數器的 key 設為weibo:weibo_idhash的 field 為like_numbercomment_numberforward_numberview_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:followA:follower的交集得到與 A 互相關注的使用者。當 A 進入另一個使用者 B 的主頁後,A:followB:follow的交集便是 A 和 B 的共同專注,A:followB:follower的交集便是 A 關注的人也關注了 B。

分散式鎖

在 Redis 2.6.12 版本開始,stringset命令增加了三個引數:

  • EX:設定鍵的過期時間(單位為秒)

  • PX:設定鍵的過期時間(單位為毫秒)

  • NX | XX:當設定為NX時,僅當 key 存在時才進行操作,設定為XX時,僅當 key 不存在才會進行操作

    由於這個操作是原子性的,可以簡單地以此實現一個分散式的鎖,例如:

set key "lock" EX 1 XX

如果這個操作返回false,說明 key 的新增不成功,也就是當前有人在佔用這把鎖。而如果返回true,則說明得了鎖,便可以繼續進行操作,並且在操作後通過del命令釋放掉鎖。並且即使程式因為某些原因並沒有釋放鎖,由於設定了過期時間,該鎖也會在 1 秒後自動釋放,不會影響到其他程式的執行。

倒排索引

倒排索引是構造搜尋功能的最常見方式,在 Redis 中也可以通過set進行建立倒排索引,這裡以簡單的拼音 + 字首搜尋城市功能舉例:

假設一個城市北京,通過拼音詞庫將北京轉為beijing,再通過字首分詞將這兩個詞分為若干個字首索引,有:北京bbebeijinbeijing。將這些索引分別作為set的 key(例如:index:北)並儲存北京的 id,倒排索引便建立好了。接下來只需要在搜尋時通過關鍵詞取出對應的set並得到其中的 id 即可。

個人能力侷限目前只知道這些資料型別的應用場景,如果各位有其他場景的應用經驗歡迎交流補充,另外面試時被問到為何使用Redis不要簡單的說因為快, 如果在系統中只使用了快取這一個應用場景那麼最起碼可以提供一些MySQL的QPS和Redis的QPS資料或者程式在Redis使用前後的平均響應時長來印證你的觀點。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章