文章目錄
取得 Redis 中指定 key 條件的筆數
公司有個流量很大的 ASP.NET MVC 網站仍在使用 Session,並利用 Redis 來儲存 Session 資訊,而近來的大型活動讓 Redis 壓力倍增,使用的 memory 是活動前的 2-3 倍,於是興起清查 Redis 各式來源使用量的念頭
經過嘗試了幾種做法,對最後解決方式的執行時間還算滿意,順手紀錄一下各種方式的優缺點待日後備查
使用 KEYS
- 官方文件請參考:KEY
- 用法:
KEYS pattern
- 說明:會列出所有符合
pattern
的 key 實際範例
語法
keys urn:session:*
結果
缺點:Redis Key 數量大時會造成 blocking
- 超過 101 萬 key 列出所有 key 需耗時 35.87 秒
使用 SCAN
- 官方文件請參考:SCAN
- 用法:
SCAN cursor [MATCH pattern] [COUNT count]
說明:
透過 cursor 來批次取回符合
pattern
指定count
數量的 key,透過重複指定回傳的 cursor 當做參數直到 cursor 為0
即完整取回所有 keyMATCH
用來指定 key 比對條件COUNT
預設為10
,一次取回10
筆
實際範例
符合
urn:session:*
有 18 筆為例,- 語法與結果
cursor 為
0
取得預設 10 筆資訊scan 0 MATCH urn:session:*
cursor 為
2
得到 cursor 重置為0
取完所有內容scan 2 MATCH urn:session:*
缺點:數量大時逐一輸入不僅耗時且容易出錯
使用 C# 的 StackExchange.Redis 套件
前面有提到
KEYS
會造成 blocking 而SCAN
則是操作不便容易出錯,所以就該使用更簡單的做法來達成目的:StackExchange.Redis 的 Keys
語法,需要特別注意的是Keys
是針對server
跟平常儲存資料的針對 database 語法不同,另外 StackExchange.Redis 沒有為SCAN
建立專屬的語法,執行Keys
時只要能轉換為SCAN
的行為皆會使用SCAN
指令,無法轉換的才會使用KEYS
指令
寫法一:將
server
加至 redis factory 中public static class RedisConnectionFactory { private static readonly Lazy<ConnectionMultiplexer> Connection; public static IServer RedisServer; static RedisConnectionFactory() { #region -- sample 1 -- var connectionString = "localhost:6379,password=password"; var options = ConfigurationOptions.Parse(connectionString); #endregion #region -- sample 2 -- // var options = new ConfigurationOptions() // { // EndPoints = { { "127.0.0.1",6379 }, { "127.0.0.1", 6380}}, // Password="password" // }; #endregion Connection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(options)); RedisServer = GetConnection.GetServer(options.EndPoints.First()); } public static ConnectionMultiplexer GetConnection => Connection.Value; public static IDatabase RedisDB => GetConnection.GetDatabase(); }
寫法二:程式中指定 server
redis factory
public static class RedisConnectionFactory { private static readonly Lazy<ConnectionMultiplexer> Connection; static RedisConnectionFactory() { #region -- sample 1 -- var connectionString = "127.0.0.1:6379,127.0.0.1:6380,password=password"; var options = ConfigurationOptions.Parse(connectionString); #endregion #region -- sample 2 -- // var options = new ConfigurationOptions() // { // EndPoints = { { "127.0.0.1",6379 }, { "127.0.0.1", 6380}}, // Password="password" // }; #endregion Connection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(options)); } public static ConnectionMultiplexer GetConnection => Connection.Value; public static IDatabase RedisDB => GetConnection.GetDatabase(); }
指定 server
//取得第一個的 Redis EndPoint var item = RedisConnectionFactory.GetConnection.GetEndPoints(true).FirstOrDefault(); //透過 Redis EndPoint 取得 Redis server var server = RedisConnectionFactory.GetConnection.GetServer(item);
實際取得 keys 及筆數
取得所有 key
server.Keys();
指定 pattern 取得所有 key
server.Keys(pattern:"urn:session:*",cursor:0);
指定 pattern 的 key 數量
server.Keys(pattern:"urn:session:*",cursor:0).Count()
key 數量很多時記得加上
pageSize
server.Keys(pattern:"urn:session:*",cursor:0,pageSize:10000)
實際效果比較
約莫為 1百萬資料
未加
pageSize
,預設使用10
:耗時41.4
秒加上
pageSize:10000
:耗時4.5
秒
心得
原本收到需求時,就傻傻地直接去開 GUI 透過 pattern 過濾筆數,約 30 秒後 GUI crash 了XD,立馬改透過 redis-cli 直接下 KEYS
(心臟真是大顆 @@“) 雖然跑得出結果但 blocking 其他指令超過 30 秒,這多跑個幾次絕對會被吊起來打呀
雖然嘗試使用 SCAN
,但跑幾個 cursor 後我自己就亂掉了,所幸查到可以用 StackExchange.Redis 的 Keys 指令來處理,加上背後也是走 SCAN
對效能影響較小,只是使用預設值 (COUNT:10) 執行時間上並沒有改善,經過嘗試不同的參數後執行時間才有明顯的下降
雖然影響時間縮短許多,但 StackExchange.Redis 仍建議不該在 production 環境上使用 Keys
指令,若真的有需要也建議在 slave 上執行以縮小影響範圍
參考資訊
文章作者 Yowko Tsai
上次更新 2021-10-28
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。