最近在debug生产环境的问题时,发现了ServiceStack 4.0.60版本RedisClient存在一个非常严重的性能问题。在高并发下,PooledRedisClientManager.GetClientRedis.DisposeClient会导致High CPU,并且持续非常长的时间才能自动修复。下面是Demo程序压测还原问题后,工具的分析结果。

通过分析源代码发现:原来获取RedisClient的逻辑中通过锁方式实现,并且当连接被占满后再获取连接时,需要循环遍历数组中所有的连接对象判断是否有可用连接,会非常消耗CPU。Dispose方法也存在循环遍历的问题。尝试了很多种修改方案后,都不尽人意,果断把这两段逻辑重写,下面是相关代码,已经经过压测。

PooledRedisClientManager.cs:

        private ConcurrentQueue<RedisClient> deactiveClientQueue = new ConcurrentQueue<RedisClient>();
private static object lckObj = new object();
private static object waitObj = new object();
private int redisClientSize = 0;
private int maxRedisClient = 500; //PooledRedisClientManager的构造函数中初始化此值:maxRedisClient = this.Config.MaxWritePoolSize;

//GetReadOnlyClient方法也可按此方式修改
public IRedisClient GetClient()
{
RedisClient client = null;
var poolTimedOut = false;
DateTime startTime = DateTime.Now; while (true)
{
bool getResult = deactiveClientQueue.TryDequeue(out client); if (getResult == false)
{
if (redisClientSize >= maxRedisClient)
{
Thread.Sleep(3);
if (PoolTimeout.HasValue)
{
// wait for a connection, cry out if made to wait too long
if ((DateTime.Now - startTime).TotalMilliseconds >= PoolTimeout.Value)
{
poolTimedOut = true;
break;
}
} }
else
{
client = CreateRedisClient();
if (client != null)
return client;
}
}
else
{
if (client != null)
{
InitClient(client);
return client;
}
else
{
client = CreateRedisClient();
if (client != null)
return client;
}
}
} if (poolTimedOut == true)
{
throw new TimeoutException(PoolTimeoutError);
} return client;
} private RedisClient CreateRedisClient()
{
if (redisClientSize >= maxRedisClient)
return null;
lock (lckObj)
{
if (redisClientSize >= maxRedisClient)
return null;
Random dom = new Random((int)DateTime.Now.Ticks);
var newClient = InitNewClient(RedisResolver.CreateMasterClient(dom.Next(100)));
newClient.OnDispose += (isRecycle) =>
{
if (isRecycle == true)
{
try
{
deactiveClientQueue.Enqueue(newClient);
}
catch
{
lock (lckObj)
{
redisClientSize--;
}
}
}
else
{
lock (lckObj)
{
redisClientSize--;
}
}
};
redisClientSize++; return newClient;
}
}
RedisClient.cs:
public event RedisClientDisposeEventHandler OnDispose;
public override void Dispose()
{
if (OnDispose != null)
OnDispose(this.HadExceptions == false);
base.Dispose();
}
RedisClient.cs:    
public delegate void RedisClientDisposeEventHandler(bool isRecycle);

下面是修改前后的结果对比:

1.100个线程,每个线程完成2000次Redis调用,每次调用GetClient。 改造前12s,改造后8.5s,提升近50%。老版本CPU消耗稍高,并具有持续性。

2.200个线程,每个线程完成2000次Redis调用,每次调用GetClient。 改造前378s,改造后19s,提升提升近20倍。老版本CPU消耗非常高(解决100%),并具有持续性。新版本CPU占用了仅有原来的一半。

3.300个线程,每个线程完成2000次Redis调用,每次调用GetClient。 改造前1580s(26分钟),改造后29s,提升提升近55倍老版本CPU消耗非常高(解决100%),并具有持续性。新版本CPU占用了仅有原来的一半。

通过上述三个场景的测试可以看出,当RedisClient访问压力持续增加时,原版本的响应时间呈现指数性增长,当达到一定压力时,RedisClient访问几乎阻塞,需要非常长时间才能缓解。重构后的RedisClient在性能上有大幅度提升,特别是在高并发下的性能表现,直接秒杀原版本!

.net ServiceStack.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. Redis性能调优:保存SNAPSHOT对性能的影响

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

  5. StackExchange.Redis性能调优

    大家经常出现同步调用Redis超时的问题,但改成异步之后发现错误非常少了,但却可能通过前后记日志之类的发现Redis命令非常慢. PS: 以后代码都在Windows bash中运行,StackExch ...

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

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

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

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

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

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

  9. Redis基础与性能调优

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

随机推荐

  1. Deployment options

    Play applications can be deployed virtually anywhere: inside Servlet containers, as standalone serve ...

  2. js事件处理、事件对象

    事件类型分类: 1 添加在html结构中的事件 <div id="div1" onclick="alert('append click event in html' ...

  3. 巧用CSS实现分隔线

    下面是几种简单实现分隔线的方法,个人比较喜欢第二种,我也给出了最后第五种比较2的写法,请大家拍砖,或者提供其他好的方法. 单个标签实现分隔线: 点此查看实例展示 .demo_line_01{ padd ...

  4. 春节快乐!推荐一个关于 SharePoint 和 BI 的视频,笑死我了

    在春节即将来临的日子里,我偶然看到了这个相见恨晚的视频:Attractive Business Intelligence 我几乎是从头笑到尾看完的,太有趣儿了!特别适合放假.过节的时候看.本来今天下午 ...

  5. 利用UICollectionViewFlowLayout的隐式动画实现UICollectionView的layout的动画调整(外加放大指定cell效果)

    前几天在gitHub看到个不错的效果,就是DaiExpandCollectionView,效果如图:   所以赶紧下下来源码看看他怎么实现的,打开源码看了半天,发现他没写什么关于动画的代码啊... 经 ...

  6. sqlserver下调试sql语句

    现在版本的sqlServer已经支持调试功能了,但是在使用的时候用到的却很少(毕竟print习惯了..) 这里做一个笔记,简单的说明一下在sqlserver下调试的方法: declare @i int ...

  7. JNI笔记1

    一.什么是JNI Java Native Interface(JNI)是Java语言的本地编程接口 是 Java 与操作系统本地代码互相调用的功能的接口 二.Java 调用C/C++步骤: 1.在Ja ...

  8. 谈谈iOS中的锁

    1 前言 近日工作不是太忙,刚好有时间了解一些其他东西,本来打算今天上午去体检,但是看看天气还是明天再去吧,也有很大一个原因:就是周六没有预约上!闲话少说,这里简单对锁来个简单介绍分享. 2 目录 第 ...

  9. Gui系统之View体系(2)---View的setContent

    1.从SetContentView讲起 1.1Activty的setContentView里面的内容 public void setContentView(@LayoutRes int layoutR ...

  10. Oracle shutdown immediate无法关闭数据库解决方法

    在测试服务器上使用shutdown immediate命令关闭数据库时,长时间无法关闭数据库,如下所示 1: [oracle@DB-Server admin]$ sqlplus / as sysdba ...