zoukankan      html  css  js  c++  java
  • .net ServiceStack.Redis 性能调优

         最近在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在性能上有大幅度提升,特别是在高并发下的性能表现,直接秒杀原版本!

  • 相关阅读:
    【python学习笔记】字符串格式化
    React-Props 一/列表渲染/条件渲染
    搜索电影小demo-react版(10.5-10.6)
    todolist-react版(9.20-9.21)
    el-form、form 等表单校验哪些事
    iframe 详解-在vue中使用iframe/iframe在vue中使用
    jmeter-beanshell 前置处理器 传参
    jmeter-beanshell-Typed variable declaration
    java 获取当前时间的年份、月份、周数
    jmeter-java.net.URISyntaxException: Illegal character in query at index 76
  • 原文地址:https://www.cnblogs.com/vveiliang/p/6185837.html
Copyright © 2011-2022 走看看