StackExchange.Redisパフォーマンスのチューニング

11043 ワード

同期呼び出しRedisがタイムアウトするという問題はよくありますが、非同期に変更するとエラーが非常に少なくなりますが、Redisコマンドが非常に遅いことが前後ログなどで発見される可能性があります.
PS:以降、コードはWindows bashで実行されます.StackExchange.Redisバージョンは1.2.6
まず問題を迅速に再現し、問題を解決し、次のコードを実行します.
public static async Task Main(string[] args)
{
    ThreadPool.SetMinThreads(8, 8);
    using (var connection = await ConnectionMultiplexer.ConnectAsync("localhost"))
    {
        connection.PreserveAsyncOrder = false;

        var db = connection.GetDatabase(0);
        var sw = Stopwatch.StartNew();

        await Task.WhenAll(Enumerable.Range(0, 10)
            .Select(_ => Task.Run(() =>
            {
                db.StringGet("aaa");

                Thread.Sleep(1000);
            })));

        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

チェックアウトを実行します.Redis.RedisTimeoutException、なぜですか?現在のワークスレッドがまったく足りないため、同期待ち時間がタイムアウトしました.具体的にはソースコードをご覧ください
上のSetMinThreads(8,8)をThreadPoolに変更した.SetMinThreads(100,100)は?異常を投げないのではないでしょうか.
 
非同期インタフェースが遅くなる問題については、まず次のコードを実行します.
        public static async Task Main(string[] args)
        {
            var tcs = new TaskCompletionSource<bool>();
            var sw = Stopwatch.StartNew();

            Console.WriteLine($"Main1: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");

            var task = Task.Run(() =>
            {
                Thread.Sleep(10);
                Console.WriteLine($"Run1: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");
                tcs.TrySetResult(true);
                Console.WriteLine($"Run2: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");
                Thread.Sleep(10000);
            });

            var a = tcs.Task.ContinueWith(_ => { Console.WriteLine($"a: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}"); });
            var b = tcs.Task.ContinueWith(_ => { Console.WriteLine($"b: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}"); });
            var c = tcs.Task.ContinueWith(_ => { Console.WriteLine($"c: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}"); });

            await tcs.Task;
            Console.WriteLine($"Main2: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");
            Thread.Sleep(100);
            await Task.Delay(10);
            Console.WriteLine($"Main3: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");
        }

最終出力の結果、Run 1とMain 2は同じスレッドを使用していることがわかりましたが、Run 2のElapsedMillisecondsは基本的にRun 1に100を加えたものです.
そして呼び出しRedisコードに戻ります
static async Task Main(string[] args)
{
   ThreadPool.SetMinThreads(100, 100);
using (var connection = await ConnectionMultiplexer.ConnectAsync("localhost")) { var db = connection.GetDatabase(0); var sw = Stopwatch.StartNew(); await Task.WhenAll(Enumerable.Range(0, 10) .Select(_ => Task.Run(async () => { await db.StringGetAsync("aaa"); Thread.Sleep(100); }))); Console.WriteLine(sw.ElapsedMilliseconds); } }

出力は100以上ですか、1000以上ですか.どうして?もともとsdkには特殊な設定があるので、非同期コードの実行順序を保護し、GetDatabase行の前にコードconnectionを追加します.PreserveAsyncOrder = false;
そしてもう一度運転して結果を見てみましょうか?上からコードを作ることで基本的に非同期が遅いのはTaskCompletionSourceと関係があると判断できますが、具体的にはsdkのソースコードを見てください.
 
上の2点をまとめると、簡単にSetMinThreadsとconnectionを通過します.PreserveAsyncOrder=falseはほとんどの問題を解決することができますが、他の深い問題はどのように発見されますか?
 
次にStackExchangeについて説明します.Redisの2つの神器ConnectionCountersとIProfiler
  • はconnectionを通過した.GetCounters().Interactiveが取得したオブジェクトの後に3つのプロパティが便利ですpublic class ConnectionCounters { /// /// Operations that have been requested, but which have not yet been sent to the server /// public int PendingUnsentItems { get; } /// /// Operations that have been sent to the server, but which are awaiting a response /// public int SentItemsAwaitingResponse { get; } /// /// Operations for which the response has been processed, but which are awaiting asynchronous completion /// public int ResponsesAwaitingAsyncCompletion { get; } }
    各プロパティは、現在のredis接続の完了するコマンドが現在存在する状態を表します.PendingUnsenntItemsは、送信待ちキューがまだ送信されていないコマンドを行っていることを意味する.SentItemsAwaitingResponseは、送信されたが応答結果のコマンドがまだ受信されていないことを示します.ResponsesAwaitingAsyncCompletionは、応答のコマンドを受信したが、TaskCompletionSource()が呼び出されていないことを示す.TrySetResult()のコマンドです.PendingUnsenntItemsとSentItems AwaitingResponseが大きすぎる原因は、基本的にネットワークがブロックされているためです.ネットワーク帯域幅やredisのvalueが大きいかどうかを確認する必要があります.ResponsesAwaitingAsyncCompletionは、上記の例のコードのようにawait以降のコードのため、スレッドが長い同期時間を費やし、コードの最適化とPreserveAsyncOrderをfalseに設定する必要がある.
  • ConnectionCountersはスレッドの瞬時状態を分析していますが、IProfilerはリクエストが合計でどれだけのredisコマンドを実行したか、それぞれどのくらいの時間を使用したかを追跡することができます.具体的な詳細は、コード体験を書いてください.参照文書
  •  
    問題を発見するには問題を解決しなければならないし、深く勉強しなければ問題を解決できない.私は文章を書くのが好きではありませんが、最近redisがタイムアウトしたという問題がいくつかあることに気づきました.結局、私は自分のピットを踏む心得をみんなに分かち合いたいと思っています.
    これはいいニュースです.それはStackExchangeです.Redis 2.0は非同期キューを再構築し、パイプ方式を用いて非同期の遅い問題を解決した.
    転載先:https://www.cnblogs.com/qhca/p/9347604.html