專案上遇到有個情境是針對 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 意見:
張貼留言