StackExchange.Redis性能调优

大家经常出现同步调用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);
    }
}

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

如果将上面的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。

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

    总结上面两点,简单得通过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已经从重构了异步队列,使用管道方式解决异步慢的问题

原文地址:https://www.cnblogs.com/qhca/p/9347604.html

时间: 2024-10-08 23:07:51

StackExchange.Redis性能调优的相关文章

redis性能调优笔记(can not get Resource from jedis pool和jedis connect time out)

对这段时间redis性能调优做一个记录. 1.单进程单线程 redis是单进程单线程实现的,如果你没有特殊的配置,redis内部默认是FIFO排队,即你对redis的访问都是要在redis进行排队,先入先出的串行执行. 之所以能够保持高性能是因为以下3点: 1)内存操作 2)数据结构简单 3)大多数是hash操作 redis基本的命令耗时都是us级别的,所以及时是单进程单线程,也能保证很高的QPS. 2.can not get Resource from jedis pool和jedis con

Redis性能调优:保存SNAPSHOT对性能的影响

前一段时间,开发环境反馈,Redis服务器访问非常慢,每个请求要数秒时间,重启之后2~3天又会这样. 我查看了一下Linux的性能,没有什么问题.通过 # redis-cli --latency 发现访问Redis确实很慢,执行info要几秒时间.里面有个参数已连接的客户端几万个,通过 Redis>client list 查看到很多client的age都很大,一直没有释放.于是怀疑是不是和这个有关,因为版本是2.8.6,无法通过client一次性kill掉所有的连接,只能写一个程序,一个一个地k

Redis性能调优

Redis性能调优 尽管Redis是一个非常快速的内存数据存储媒介,也并不代表Redis不会产生性能问题.前文中提到过,Redis采用单线程模型,所有的命令都是由一个线程串行执行的,所以当某个命令执行耗时较长时,会拖慢其后的所有命令,这使得Redis对每个任务的执行效率更加敏感. 针对Redis的性能优化,主要从下面几个层面入手: 最初的也是最重要的,确保没有让Redis执行耗时长的命令 使用pipelining将连续执行的命令组合执行 操作系统的Transparent huge pages功能

Redis基础、高级特性与性能调优

本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导.本文适合使用Redis的普通开发人员,以及对Redis进行选型.架构设计和性能调优的架构设计人员. 目录 概述 Redis的数据结构和相关常用命令 数据持久化 内存管理与数据淘汰机制 Pipelining 事务与Scripting Redis性能调优 主从复制与集群分片 Redis Java客户端的

Redis 基础、高级特性与性能调优

本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导. 本文适合使用Redis的普通开发人员,以及对Redis进行选型.架构设计和性能调优的架构设计人员. 目录 概述 Redis的数据结构和相关常用命令 数据持久化 内存管理与数据淘汰机制 Pipelining 事务与Scripting Redis性能调优 主从复制与集群分片 Redis Java客户端

性能调优攻略

关于性能优化这是一个比较大的话题,在<由12306.cn谈谈网站性能技术>中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈谈性能优化,主要是一些代码级别的技术和方法.本文的东西是我的一些经验和知识,并不一定全对,希望大家指正和补充. 在开始这篇文章之前,大家可以移步去看一下酷壳以前发表的<代码优化概要>,这篇文章基本上告诉你--要进行优化,先得找到性能瓶颈! 但是在讲如何定位系统性能瓶劲之前,请让我讲一下系统性能的定义和测试,因为没有这两件事,后

linux性能调优总结

系统性能一直是个热门话题.做运维这几年也一直在搞性能调优,写这个文章也算是对工作的总结. 讲调优第一步是,要讲为什么要调优?也就是系统分析,分析还需要有指标,做好性能监控的情况下,看到确实需要调优才能进行.不能为了调优而 “调优“ 那不是调优,那是破坏. 性能分析的目的 找出系统性能瓶颈 为以后的优化提供方案或者参考 达到良好利用资源的目的.硬件资源和软件配置. 影响性能的因素 想确定有哪些因素,首先确定你的应用是什么类型的?例如: cpu密集型例如web服务器像nginx node.js需要C

[转载] 性能调优攻略

原文: http://coolshell.cn/articles/7490.html 作为架构工程师, 性能调整是平时经常需要做的工作了, 这篇文章对性能调优方面做了一个很好的综述, 不知道怎么入手的同学们, 赶紧看一下吧 关于性能优化这是一个比较大的话题,在<由12306.cn谈谈网站性能技术>中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈谈性能优化,主要是一些代码级别的技术和方法.本文的东西是我的一些经验和知识,并不一定全对,希望大家指正和补充. 在开

转载:性能调优攻略

关于性能优化这是一个比较大的话题,在<由12306.cn谈谈网站性能技术>中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈谈性能优化,主要是一些代码级别的技术和方法.本文的东西是我的一些经验和知识,并不一定全对,希望大家指正和补充. 在开始这篇文章之前,大家可以移步去看一下酷壳以前发表的<代码优化概要>,这篇文章基本上告诉你——要进行优化,先得找到性能瓶颈! 但是在讲如何定位系统性能瓶劲之前,请让我讲一下系统性能的定义和测试,因为没有这两件事,后