天天看點

Redis中有幾百萬資料量,如何進行高效通路?

前言

Redis 作為一個高性能的 key-value 資料庫,在項目中我們經常會用到。尤其需要知道一些字首的key值,那我們怎麼去檢視呢?通常情況下,Redis 中的資料都是海量的,我們通路Redis中的資料時,一定要做到心中有數,避免資料量超過預期時的一些事故。

問題産生

系統中有個項目需要登陸時統計線上使用者的數量,由于使用 spring session 做了分布式 Session,把所有session都存儲到了 Redis 中。而 spring session 又不支援擷取所有線上使用者的操作,是以隻能檢視 session 在 Redis 中的存儲格式來自己統計。經過分析,spring session 在 Redis 中存儲的 session 值時都是以 **【spring:session:sessions:】**作為字首,是以我最初的解決方式是,使用 keys 指令擷取所有的 session,手動統計 session 中的使用者數 。

後來系統上線發現登陸操作變得非常慢,檢視日志定位到問題,發現就是 keys 指令導緻。

分析原因

後來發現線上 Redis 中 session 的數量有上百萬,因為 一個使用者可能會産生多個session,導緻 Redis 中實際 sesion 的數量遠大于登陸使用者數。

keys 指令是周遊算法,發複雜度為 O(n) ,資料量達到幾百萬,keys這個指令就會耗時較長。甚至會導緻Redis 服務卡頓,假死,因為 Redis 是單線程程式,其它指令必須等到目前的 keys 指令執行完之後才可以繼續。

解決方案

既然 keys 指令産生了性能問題,那有沒有其它更好的 指令來代替呢?去 Redis 的官網檢視 keys 指令的作用時,發現有一個 Warring:

Warning: consider ​​KEYS​​​ as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don’t use ​​KEYS​​​ in your regular application code. If you’re looking for a way to find keys in a subset of your keyspace, consider using ​​SCAN​​​ or ​​sets​​.

建議我們生産環境最好不要使用 keys 指令,應該使用 scan 或者 sets 。

檢視文檔我們發現 scan 指令有如下特點:

  • 複雜度雖然也是 O(n),但是它是通過遊标分步進行的,不會阻塞線程。
  • 提供 count 參數,不是結果數量,是Redis單次周遊字典槽位數量(約等于)。
  • 同 keys 一樣,它也提供模式比對功能。
  • 伺服器不需要為遊标儲存狀态,遊标的唯一狀态就是 scan 傳回給用戶端的遊标整數。
  • 傳回的結果可能會有重複,需要用戶端去重複,這點非常重要。
  • 單次傳回的結果是空的并不意味着周遊結束,而要看傳回的遊标值是否為零。

說白了就是 scan 指令允許增量疊代,每次調用隻傳回少量元素,是以可以在生産中使用它們,而不會像 keys 或者 smembers 這樣的指令可能會在調用時長時間阻塞 Redis 伺服器,或者傳回一個超大集合,壓垮壓垮伺服器記憶體。

SCAN 指令使用

scan 指令格式

SCAN cursor [MATCH pattern] [COUNT count]      

指令解釋

scan 遊标 MATCH <傳回和給定模式相比對的元素> count 每次疊代所傳回的元素數量。

SCAN指令傳回的是一個遊标,從0開始周遊,到0結束周遊。

示例

127.0.0.1:6379[1]> scan 0 match spring:session:clinic:sessions:* count 10
1) "10"
2) 1) "spring:session:clinic:sessions:expires:a797bc05-d53b-40ad-81e8-bcac472b639e"
   2) "spring:session:clinic:sessions:2ab96e94-a35a-4508-bd0c-6aa6a9d34b9c"
   3) "spring:session:clinic:sessions:27c89ae9-adee-4641-9186-67b18036f540"
   4) "spring:session:clinic:sessions:expires:f961a498-902a-40ec-b9e9-8179a4b9c33a"
   5) "spring:session:clinic:sessions:expires:2ab96e94-a35a-4508-bd0c-6aa6a9d34b9c"
   6) "spring:session:clinic:sessions:expires:580feda6-ebe8-4bed-b4f4-53b8302ac9a8"
   7) "spring:session:clinic:sessions:expires:27c89ae9-adee-4641-9186-67b18036f540"      

SCAN傳回值是一個包含兩個值的數組:第一個值是在下一個調用中使用的新遊标,第二個值是元素數組。

上面指令表示:從 0 開始周遊,傳回了遊标 10 ,又傳回了資料,繼續 scan 周遊,就要從 10 開始

需要注意的是:​​SCAN​​系列函數不保證每次調用傳回的元素數量在給定範圍内。這些指令也允許傳回零元素,隻要傳回的遊标不為零,用戶端就不應該認為疊代完成。

總結

keys 和 scan 的用法我們要搞清楚,生産環境不要使用keys,會存在安全隐患,這是我們在工作的過程經常會忽略的。

最後

你以為我最後 使用了 scan 統計了線上使用者數??? 不不不,我還是用的 keys 指令(手動狗頭),因為我換了一個key,這個key的資料量就是登陸使用者數,見 ​​分布式 Session 之 Spring Session 架構與設計​​

什麼??? 你說我不怕這個key的數量也會變得很大嗎?是誰給我的勇氣?

當然是梁…哦不對,是官網上有句話給了我這個勇氣:

參考:

  • ​​https://redis.io/commands/keys​​
  • ​​https://redis.io/commands/scan​​