專案上遇到有個情境是針對 Redis 的 List 做大量新增的動作,但在新增的同時又希望兼顧效能,因此這篇文章是研究在 StackExchange.Redis 提供的 API 中幾個新增 List 的方式,如何使用以及簡單測試多筆資料時消耗的時間比較,若是有不清楚或是錯誤的地方歡迎討論予糾正。
批次新增 List 型別
由於考量到使用情境是類似 Queue 順序性是重要的,因此是透過 Redis 中的 List 型別做研究與測試,根據 StackExchange.Redis 提供的 API Document 批次新增多筆 List 型別可以使用下列方式
方式一 : ListRightPush
使用迴圈方式逐一的呼叫 ListRightPush 方法將資料新增到 Cache Server 上,缺點是每次請求都需要等待回傳的結果,如果在一次新增大量資料或是網路狀況不穩定時,有可能會發生 Timeout 的狀況
方式二 : CreateBatch
CreateBatch 是把需要執行的指令打包成一個 script 送出去,接著等待指令執行的結果;使用方式是一開始使用 CreateBatch 指令,最後接 Execute 方法送出
方式三 : Lua Script
在 Redis 中也支援 Lua script,Lua script 是一個輕量級內嵌式的程式語言,在 StackExchange.Redis 官網有提供使用 Lua script 語法說明文件 script,應用在此情境可以改為下列範例
方式四 : FireAndForget
第一種的方式是會等待 Redis Server 新增結果,但如果你不想等待回傳結果時,可以設定 flags 為 fireAnd Forget,也就是俗稱的射後不理
由於考量到使用情境是類似 Queue 順序性是重要的,因此是透過 Redis 中的 List 型別做研究與測試,根據 StackExchange.Redis 提供的 API Document 批次新增多筆 List 型別可以使用下列方式
- ListRightPush
- CreateBatch
- Lua Script
- FireAndForget
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => { string cacheConnection = "127.0.0.1:6379,syncTimeout =3000"; return ConnectionMultiplexer.Connect(cacheConnection); }); private static ConnectionMultiplexer RedisConnection => lazyConnection.Value;
方式一 : ListRightPush
使用迴圈方式逐一的呼叫 ListRightPush 方法將資料新增到 Cache Server 上,缺點是每次請求都需要等待回傳的結果,如果在一次新增大量資料或是網路狀況不穩定時,有可能會發生 Timeout 的狀況
private static void InsertBy_ListRightPush(IEnumerable<string> input) { foreach (var entity in input) { RedisConnection.GetDatabase().ListRightPush(name, entity); } }
方式二 : CreateBatch
CreateBatch 是把需要執行的指令打包成一個 script 送出去,接著等待指令執行的結果;使用方式是一開始使用 CreateBatch 指令,最後接 Execute 方法送出
private static void InsertBy_CreateBatch(IEnumerable<string> input) { var batch = RedisConnection.GetDatabase().CreateBatch(); foreach (var entity in input) { RedisConnection.GetDatabase().ListRightPush(name, entity); } batch.Execute(); }
方式三 : Lua Script
在 Redis 中也支援 Lua script,Lua script 是一個輕量級內嵌式的程式語言,在 StackExchange.Redis 官網有提供使用 Lua script 語法說明文件 script,應用在此情境可以改為下列範例
private static void InsertBy_LuaScript(IEnumerable<string> input) { var name = MethodBase.GetCurrentMethod().Name; StringBuilder sb = new StringBuilder(); foreach (var item in input) { sb.AppendLine($"redis.call('rpush', 'Luascript', {item})"); } var prepared = LuaScript.Prepare(sb.ToString()); RedisConnection.GetDatabase().ScriptEvaluate(prepared); }其中可以看到 redis.call 第一個參數為 rpush (List type);第二個參數為對應的 RedisKey;第三個參數為需要新增的資料,以此情境來說新增資料為 000001、000002、000003,因此需要針對輸出集合做轉換,將000001 前後加上 ''變為 '000001',對應到 Lua script 指令則為 rpush 'luascript' '000001','000002','000003'
方式四 : FireAndForget
第一種的方式是會等待 Redis Server 新增結果,但如果你不想等待回傳結果時,可以設定 flags 為 fireAnd Forget,也就是俗稱的射後不理
private static void InsertBy_FireAndForget(IEnumerable<string> input) { foreach (var entity in input) { RedisConnection.GetDatabase().ListRightPush(name, entity, flags: CommandFlags.FireAndForget); } }
以上針對其四種方式介紹其使用方式,接著想要針對特定筆數花費的時間做比較,在每個新增的 function 加上 stopwatch 時間戳記,來了解各自所花費的時間,透過新增成功後的數據資料作為應用前的評估
前置作業
Server : 在本機環境使用 Docker 架設 Redis Server,版本為5.0.3
Client : 測試專案為 .Net Framework Console App
系統資訊
- Windows 10 專業版
- CPU : i7-8550U @1.8G
- 記憶體 : 16G
測試情境
- 資料型態 List : 專案使用情境需求是要用到 Queue,測試資料型態為 List
- 新增資料方式 : 使用 StackExchange.Redis 提供的 ListRightPush、CreateBatch、Lua Script 以及 FireAndForget
測試代碼
根據前面提到的加上時間戳記,更新後的 sample code 如下
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using StackExchange.Redis; namespace Redis { class Program { private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => { string cacheConnection = "127.0.0.1:6379,syncTimeout =3000"; return ConnectionMultiplexer.Connect(cacheConnection); }); private static ConnectionMultiplexer RedisConnection => lazyConnection.Value; private static Stopwatch sw = new Stopwatch(); static void Main(string[] args) { cleaUpData(); // initial Data ex : 000001, 000002, 000003..etc var count = 100; var data = new HashSet<string>(); for (int i = 0; i < count; i++) { data.Add(i.ToString().PadLeft(6, '0')); } // use single insert InsertBy_ListRightPush(data); // createBatch InsertBy_CreateBatch(data); // Lua script InsertBy_LuaScript(data); // use single insert & fireAndForget InsertBy_FireAndForget(data); Console.ReadKey(); } private static void cleaUpData() { var RedisDB = RedisConnection.GetDatabase(); RedisDB.KeyDelete("ListRightPush"); RedisDB.KeyDelete("FireAndForget"); RedisDB.KeyDelete("CreateBatch"); RedisDB.KeyDelete("Luascript"); } private static void InsertBy_LuaScript(IEnumerable<string> input) { var name = MethodBase.GetCurrentMethod().Name; sw.Reset(); sw.Start(); StringBuilder sb = new StringBuilder(); foreach (var item in input) { sb.AppendLine($"redis.call('rpush', 'Luascript', {item})"); } var prepared = LuaScript.Prepare(sb.ToString()); RedisConnection.GetDatabase().ScriptEvaluate(prepared); sw.Stop(); Console.WriteLine($"{name} total cost : {sw.ElapsedMilliseconds}"); } private static void InsertBy_CreateBatch(IEnumerable<string> input) { var name = MethodBase.GetCurrentMethod().Name; var batch = RedisConnection.GetDatabase().CreateBatch(); sw.Reset(); sw.Start(); foreach (var entity in input) { RedisConnection.GetDatabase().ListRightPush(name, entity); } sw.Stop(); batch.Execute(); Console.WriteLine($"{name} total cost : {sw.ElapsedMilliseconds}"); } private static void InsertBy_FireAndForget(IEnumerable<string> input) { var name = MethodBase.GetCurrentMethod().Name; sw.Reset(); sw.Start(); foreach (var entity in input) { RedisConnection.GetDatabase().ListRightPush(name, entity, flags: CommandFlags.FireAndForget); } sw.Stop(); Console.WriteLine($"{name} total cost : {sw.ElapsedMilliseconds}"); } private static void InsertBy_ListRightPush(IEnumerable<string> input) { var name = MethodBase.GetCurrentMethod().Name; sw.Reset(); sw.Start(); foreach (var entity in input) { RedisConnection.GetDatabase().ListRightPush(name, entity); } sw.Stop(); Console.WriteLine($"{name} total cost : {sw.ElapsedMilliseconds}"); } } }
數據結果
method | 500 | 1000 | 10000 |
---|---|---|---|
ListRightPush | 403 | 991 | 6665 |
CreateBatch | 447 | 698 | 6701 |
Lua Script | 38 | 31 | 143 |
FireandForget | 9 | 12 | 201 |
心得
這篇介紹幾種新增 List 的方式與其簡單測試數據資料,排除掉射後不理來看(因為根本不會知道成功或失敗),用 Lua script 新增大量資料的回應時間都是最快的,其次是 createBatch 方式,可以提供需要使用的人數據與思考方向,原本想測試更多筆數的數據,但由於近期筆電不知原因每小時都會聽到風扇的哀號聲,有時覺得像是飛機要起飛了一樣,對於測試來說是一大挑戰,或許等到筆電康復之後再進行更大筆數據的測試,之後如果有新數據會在分享出來。
參考
StackExchange.Redis
0 意見:
張貼留言