大家经常出现同步调用Redis超时的问题,但改成异步之后发现错误非常少了,但却可能通过前后记日志之类的发现Redis命令非常慢。

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

   先快速重现问题和解决问题,大家先运行下面的代码

public static async Task Main(string[] args)
{
ThreadPool.SetMinThreads(, );
using (var connection = await ConnectionMultiplexer.ConnectAsync("localhost"))
{
connection.PreserveAsyncOrder = false; var db = connection.GetDatabase();
var sw = Stopwatch.StartNew(); await Task.WhenAll(Enumerable.Range(, )
.Select(_ => Task.Run(() =>
{
db.StringGet("aaa"); Thread.Sleep();
}))); 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();
Console.WriteLine($"Run1: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");
tcs.TrySetResult(true);
Console.WriteLine($"Run2: {sw.ElapsedMilliseconds}, ThreadId: {Environment.CurrentManagedThreadId}");
Thread.Sleep();
}); 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();
await Task.Delay();
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();
var sw = Stopwatch.StartNew(); await Task.WhenAll(Enumerable.Range(, )
.Select(_ => Task.Run(async () =>
{
await db.StringGetAsync("aaa"); Thread.Sleep();
}))); Console.WriteLine(sw.ElapsedMilliseconds);
}
}

你们发现输出是100多还是1000多?为什么?原来是因为sdk中有一个特殊的设置,要保护异步代码执行的顺序,然后我们在GetDatabase行之前加一个代码connection.PreserveAsyncOrder = false;

然后再运行一次看看结果是多少呢?通过上面再做代码基本上可以确定异步慢是和TaskCompletionSource和关系的,具体请看sdk的源代码。

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

下面就要介绍StackExchange.Redis两个神器ConnectionCounters和IProfiler

  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已经从重构了异步队列,使用管道方式解决异步慢的问题

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

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

    对这段时间redis性能调优做一个记录. 1.单进程单线程 redis是单进程单线程实现的,如果你没有特殊的配置,redis内部默认是FIFO排队,即你对redis的访问都是要在redis进行排队,先 ...

  2. Redis性能调优

    Redis性能调优 尽管Redis是一个非常快速的内存数据存储媒介,也并不代表Redis不会产生性能问题.前文中提到过,Redis采用单线程模型,所有的命令都是由一个线程串行执行的,所以当某个命令执行 ...

  3. Redis性能调优建议

    一. Redis部署结构优化建议 1. Master不做AOF或RDB持久化,Slave做AOF持久化,建议同时做RDB持久化 2. 所有Master全部增加Slave 3. Master挂载Slav ...

  4. .net ServiceStack.Redis 性能调优

    最近在debug生产环境的问题时,发现了ServiceStack 4.0.60版本RedisClient存在一个非常严重的性能问题.在高并发下,PooledRedisClientManager.Get ...

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

    前一段时间.开发环境反馈,Redisserver訪问很慢,每一个请求要数秒时间,重新启动之后2~3天又会这样. 我查看了一下Linux的性能,没有什么问题. 通过 # redis-cli --late ...

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

    本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导. ...

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

    本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导. ...

  8. Redis 宝典 | 基础、高级特性与性能调优

    转载:Redis 宝典 | 基础.高级特性与性能调优 本文由 DevOpsDays 本文由简书作者kelgon供稿,高效运维社区致力于陪伴您的职业生涯,与您一起愉快的成长.     作者:kelgon ...

  9. Redis基础与性能调优

    Redis是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库.缓存服务或消息服务使用. Redis支持多种数据结构,包括字符串.哈希表.链表.集合.有序集合.位图.Hyperloglogs等. ...

随机推荐

  1. Java 装饰模式(4.4)

    装饰模式(decorator pattern). 依照Num模型.讨论职业/IProfession类层次. IProfession定义了方法say(String),事实上现类教师/ Teacher.医 ...

  2. C语言printf格式化输出修饰符详解

    转换说明 输出 %a,%A 浮点数.十六进制数和p-计数法(C99) %c 一个字符 %d 有符号十进制数 %e,%E 浮点数,e计数法 %f 浮点数,十进制计数法 %g,%G 根据数值不同自动选择% ...

  3. Hibernate关联关系(二) Cascade级联

    1.cascade定义的是关系两端对象到对象的级联关系:而inverse定义的是关系和对象的级联关系. all : 所有情况下均进行关联操作.  none:所有情况下均不进行关联操作.这是默认值.  ...

  4. 重写spring cloud config 本地bootstrap

    在spring-cloud中使用了config-server之后,需要在client端加入bootstrap作为配置文件,其中通常包含如下: spring.application.name=ms-as ...

  5. G1日志分析

    1. 概述 来自对官方G1垃圾收集器的日志解释分析,官方地址:https://blogs.oracle.com/poonam/understanding-g1-gc-logs或https://blog ...

  6. 基于部标Jt/T809协议和Java Netty框架构建Gps位置监控平台

    现在地方上由于运输车辆的GPS数据都分散在地方上已有的各种企业平台上面,不利于大数据的分析和智能应用,而开发智能的基于大数据的Gps监控平台,往往需要和各种第三方的部标GPS监控平台对接,获取到第三方 ...

  7. maven+nexus setting.xml配置(收藏)

    <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...

  8. Echarts中线状图的X轴坐标标签倾斜样式

    在echarts中应用线状图时可以展现很多的数据,而当数据量过多的时候,X轴的坐标就会显示不全,因为整个图形的宽度是一定的,X轴的全长是一定的 http://www.cnblogs.com/phpgc ...

  9. 将td中文字过长的部分变成省略号显示的小技巧

    首先设置表格的样式table-layout:"fixed"再设置表格的宽度(这步必须) 最后再设置td样式的三个必要属性 代码如下: text-overflow: ellipsis ...

  10. php yaf框架扩展实践五——数据层

    从狭义角度上来理解数据层就是数据库,比较广义的理解来看数据库.远程数据.文件等都可以看做数据层.项目初期的时候一般单一的数据库就可以了,随着流量的增大就要对数据层做很多的改进,例如增加从库分散读压力, ...