StackExchange.Redis性能调优

    你们常常出现同步调用Redis超时的问题,但改为异步以后发现错误很是少了,但却可能经过先后记日志之类的发现Redis命令很是慢。git

PS: 之后代码都在Windows bash中运行,StackExchange.Redis版本为1.2.6github

   先快速重现问题和解决问题,你们先运行下面的代码redis

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

    运行发现抛出StackExchange.Redis.RedisTimeoutException,为何呢?是由于当前工做线程根本不够用,同步等待时已经超时。具体请看源代码bash

    若是将上面的ThreadPool.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}");
        }

    最终输出结果发现Run1和Main2是使用相同的线程吧,而Run2的ElapsedMilliseconds基本上就是在Run1的基础上加100。async

    而后再回到调用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的源代码spa

 

    总结上面两点,简单得经过SetMinThreads和connection.PreserveAsyncOrder = false能够解决绝大部分问题,但更多其余深层次的问题怎么发现呢?

 

    下面就要介绍StackExchange.Redis两个神器ConnectionCountersIProfiler 

  1. 经过connection.GetCounters().Interactive得到的对象以后其中有三个属性很是有用
    public class ConnectionCounters
    {
        /// <summary>
        /// Operations that have been requested, but which have not yet been sent to the server
        /// </summary>
        public int PendingUnsentItems { get; }
    
        /// <summary>
        /// Operations that have been sent to the server, but which are awaiting a response
        /// </summary>
        public int SentItemsAwaitingResponse { get; }
    
        /// <summary>
        /// Operations for which the response has been processed, but which are awaiting asynchronous completion
        /// </summary>
        public int ResponsesAwaitingAsyncCompletion { get; }
    }

    每一个属性表示当前redis链接的待完成的命令当前所处的状态。经过字面意思就能够知道PendingUnsentItems表示已经进行待发送队列还未发送出去的命令;SentItemsAwaitingResponse表示已经发送出去但尚未收到响应结果的命令;ResponsesAwaitingAsyncCompletion则表示已经收到响应的命令,但尚未调用TaskCompletionSource<T>().TrySetResult()的命令。
    其中PendingUnsentItems和SentItemsAwaitingResponse过大的缘由基本上是由于网络阻塞了,你须要检查一下网络带宽或者redis的value是否很大。
    ResponsesAwaitingAsyncCompletion则是由于await以后的代码,如上面示例中的代码,线程占用了很长的同步时间,须要优化代码和将PreserveAsyncOrder设置为false。

  2. ConnectionCounters分析的是一个线程的瞬时状态,而IProfiler则能够跟踪一个请求总共执行了多少的redis命令以及他们分别使用了多长时间,具体细节请你们写代码体验。参考文档

 

    发现问题就须要解决问题,也就须要深层次得去学习才能解决问题。我不喜欢写文章,但发现最近有好几篇说redis超时的问题,最终我仍是想把本身的踩坑的心得分享给你们。

    这在里说一个好消息,那就是StackExchange.Redis 2.0已经从重构了异步队列,使用管道方式解决异步慢的问题

相关文章
相关标签/搜索