[食譜好菜] 使用 StackExchange.Redis 對 Redis 執行批次寫入指令

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;
    }
}

參考資料

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學