Redis 的效能有體驗過的朋友應該都是有目共睹的,最近遇到一個一次要寫入 16 萬筆資料的場景,測試環境的 VM 規格都是最基本的,在測試環境測試一筆一筆寫入要大概將近 20 分鐘,而且還會 Timeout,心想即使規格不高 16 萬筆應該也不至於這麼慢吧,爬了一下文大概找到了兩種解法。
射後不理
會慢的原因在於每執行一次寫入,Client 端就要等伺服器回應,所以 Timeout 是 Client 端等到不耐煩所以發生的 Timeout,不是伺服器端回傳的 Timeout,而這種來來回回所累積起來的 Round-trip delay time 也挺恐怖的,因此第一種解法是在寫入 Cache 的時候將 flags 指定為 CommandFlags.FireAndForget
,標準的射後不理,但是我個人比較喜歡第二種做法。
public bool StringSet(string key, string value, TimeSpan expiry, int database)
{
var db = this.connection.GetDatabase(database);
return db.StringSet(key, value, expiry, flags: CommandFlags.FireAndForget);
}
批次執行寫入指令
第二種解法就是利用 IDatabase.CreateBatch()
取得 IBatch
實例,而 IBatch 介面還繼承了 IDatabaseAsync
,在 IDatabaseAsync 底下定義了很多非同步操作的方法,所以執行批次指令不只寫入指令,我們還可以在該次批次中加入刪除、搬移...等不同的指令,最後呼叫 IBatch.Execute()
送給 Redis 一次執行,底下我就我的場景實作了批次執行寫入指令的方法。
public void StringSet(List<KeyValuePair<string, string>> values, TimeSpan expiry, int database, int batchSize)
{
var db = this.connection.GetDatabase(database);
var skipped = 0;
IEnumerable<KeyValuePair<string, string>> batchedValues;
while ((batchedValues = values.Skip(skipped).Take(batchSize)).Any())
{
var batch = db.CreateBatch();
var tasks = batchedValues.Select(x => batch.StringSetAsync(x.Key, x.Value, expiry))
.Cast<Task>()
.ToArray();
batch.Execute();
Task.WaitAll(tasks);
skipped += batchSize;
}
}