李朝强的博客

人之为学,如饮河海,大饮则大盈,小饮则小颖!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

CSRedisClient拿来主义,是这么用的吗?

Posted on 2023-07-20 12:16  李朝强  阅读(250)  评论(0编辑  收藏  举报

 

      开篇先引用下鲁迅先生笔下的《拿来主义》一说,暂且粗浅的理解为,拿来就用,以为是能达到自己想要的效果。

向鲁迅先生致敬!

     算是2021年的新项目吧,内部精心规划,来年招兵买马,注入了不少新鲜血液。新人或许有新气象。老人带领下的新团队,开启了老项目的重构计划...

新项目,名义上已DDD框架为设计原则,由于团队专业素质参差不齐,加之个人经验的影响,至今都不能说是一个清晰完整的DDD项目。业内,尤其是不同平台下的社区生态,架构到框架,理论到实践,总是能够

衍生出无数可能。这里我们暂且不吐糟这些不同认知及经验构建出来的框架,或许这样可能催生出璀璨的行业文化。

 

    时隔多年,到2023年,抽出一些时间,细看了下先前项目的源代码。脑海中只是隐约记得,总是有运营和用户反馈,某某页面,第一次加载总是很慢,连续刷新几次,也快不到那儿去。于是抽查了印象中的几个页面,

的确如此,首次很慢,几十个HTTP请求,1s以上的就接近10个。而问及这个问题时,开发者的表态,总是以单表数据太大,有关键词模糊等为由,不由分说了。粗略的看了下源代码,预感出,在多级缓存方面可能存在问题。项目中,有两种缓存,一种基于进程内IMemoryCache的方式,另一种,则是接入redis。

这里先不要断言,我们不能确保IMemoryCache的形式就不会有什么问题。于是基于此,创建了两个单元测试方法。一个是在调用较慢接口(HTTP请求耗时较长,一般超过1s的,从长到短),在这个过程中执行单元测试,

按步执行,在某些位置,的确出现了卡顿现象。首次出现卡顿的位置,就是使用IMemoryCache的地方。发现,开发者,无所顾忌的,读取了整表的数据,然后放入缓存,致使这个表在数据增多时,初次从数据库读取载入,

需要较长时间。那我们就先排除这个吧,注释掉这段,临时补上一段,继续。

     全文所说项目,乃是基于.net5、mysql、Redis、docker等这些技术栈为基础。

     发现,在请求HTTP的时,进入Controller的时间,明显超出预期。试想,应该是IOC在构建Controller时消耗了较长时间,导致我们发出请求,却迟迟没有进入Action。为了进一步验证是IOC构建Controller的过程出现了问题,

我尝试请求一个简单的接口,简单到不需要通过任何协议来获取数据的方法。事实证明,造成当前阶段卡顿的原因,不在接口,而在进入接口前,经历了什么。

    再进一步分析,分析Controller构造函数,注入了什么?注入的东西有没有依赖什么服务,比如redis。初步分析,依赖外部服务的有两种,第一个种,SqlSugarClient,第二种,CSRedisClient。那么我们继续进行第二个单元测试,

尝试分辨初始化依赖SqlSugarClient和CSRedisClient的服务,分析用时较长的一方。经分析,注入依赖CSRedisClient的地方,用时较长。由于项目引用的依赖CSRedisClient的组件,为当时团队开发,且nuget包上传至自建Nuget仓库中。

那我们就要进一步分析nuget包里的东西了。

        /// <summary>
        /// 创建缓存
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        private IDistributedCache CreateCache(RedisConfig config)
        {
   var _client = new CSRedisClient($"{config.ConnectionString},prefix={config.InstanceName},testcluster=false");
returnreturn new CSRedisCache(_client); }

看上面代码,他们是自定义了一个Cache类,内部通过这个方法,来创建IDistributedCache 对象。而且项目中依赖Cache类的服务,非单例,而是Score,这样致使每次请求,都会构建一个

CSRedisClient实例,而且初始化CSRedisClient的地方,的确有卡顿。改造下吧!
  /// <summary>
        /// <![CDATA[推荐,添加Redis单例服务]]>
        /// </summary>
        /// <param name="services"></param>
        /// <param name="func"></param>
        public static void AddRedisCacheSingletonService(this IServiceCollection services, Func<RedisConfig> func)
        {
            var config = func.Invoke();
            services.TryAddSingleton(new RedisConfig
            {
                ConnectionString = config.ConnectionString,
                InstanceName = config.InstanceName
            });
            services.TryAddSingleton<ICache, Cache>();
        }

来一个单例,使用CSRedisClient时推荐使用单例模式,不够用,考虑对象池吧。有时候,不需要CSRedisClient的方法,由于Service构造函数注入,不得不构建了。这里可考虑使用懒加载Layz<T>,用法也很简单,只有使用的时候,才会被初始化,

也就是在首次使用Layz Vaue时,就初始化了。后面同一个Lazy实例,访问Value仍是同一个实例,相当于缓存了Value。

       /// <summary>
        /// 创建缓存(延迟加载)
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        private Lazy<IDistributedCache> CreateCache(RedisConfig config)
        {
            return
                new Lazy<IDistributedCache>(() =>
                {
                    _client = new CSRedisClient($"{config.ConnectionString},prefix={config.InstanceName},testcluster=false");
                    return new CSRedisCache(_client);
                });
        }

经过两个地方的调整,接口响应时间从3s+,降低到了平均267ms。

 

      开篇先引用下鲁迅先生笔下的《拿来主义》一说,暂且粗浅的理解为,拿来就用,以为是能达到自己想要的效果。

 

     算是2021年的新项目吧,内部精心规划,来年招兵买马,注入了不少新鲜血液。新人或许有新气象。老人带领下的新团队,开启了老项目的重构计划...

新项目,名义上已DDD框架为设计原则,由于团队专业素质参差不齐,加之个人经验的影响,至今都不能说是一个清晰完整的DDD项目。业内,尤其是不同平台下的社区生态,架构到框架,理论到实践,总是能够

衍生出无数可能。这里我们暂且不吐糟这些不同认知及经验构建出来的框架,或许这样可能催生出璀璨的行业文化。

 

    时隔多年,到2023年,抽出一些时间,细看了下先前项目的源代码。脑海中只是隐约记得,总是有运营和用户反馈,某某页面,第一次加载总是很慢,连续刷新几次,也快不到那儿去。于是抽查了印象中的几个页面,

的确如此,首次很慢,几十个HTTP请求,1s以上的就接近10个。而问及这个问题时,开发者的表态,总是以单表数据太大,有关键词模糊等为由,不由分说了。粗略的看了下源代码,预感出,在多级缓存方面可能存在问题。项目中,有两种缓存,一种基于进程内IMemoryCache的方式,另一种,则是接入redis。

这里先不要断言,我们不能确保IMemoryCache的形式就不会有什么问题。于是基于此,创建了两个单元测试方法。一个是在调用较慢接口(HTTP请求耗时较长,一般超过1s的,从长到短),在这个过程中执行单元测试,

按步执行,在某些位置,的确出现了卡顿现象。首次出现卡顿的位置,就是使用IMemoryCache的地方。发现,开发者,无所顾忌的,读取了整表的数据,然后放入缓存,致使这个表在数据增多时,初次从数据库读取载入,

需要较长时间。那我们就先排除这个吧,注释掉这段,临时补上一段,继续。

 

     发现,在请求HTTP的时,进入Controller的时间,明显超出预期。试想,应该是IOC在构建Controller时消耗了较长时间,导致我们发出请求,却迟迟没有进入Action。为了进一步验证是IOC构建Controller的过程出现了问题,

我尝试请求一个简单的接口,简单到不需要通过任何协议来获取数据的方法。事实证明,造成当前阶段卡顿的原因,不在接口,而在进入接口前,经历了什么。

    再进一步分析,分析Controller构造函数,注入了什么?注入的东西有没有依赖什么服务,比如redis。初步分析,依赖外部服务的有两种,第一个种,SqlSugarClient,第二种,CSRedisClient。那么我们继续进行第二个单元测试,

尝试分辨初始化依赖SqlSugarClient和CSRedisClient的服务,分析用时较长的一方。经分析,注入依赖CSRedisClient的地方,用时较长。由于项目引用的依赖CSRedisClient的组件,为当时团队开发,且nuget包上传至自建Nuget仓库中。

那我们就要进一步分析nuget包里的东西了。

        /// <summary>
        /// 创建缓存
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        private IDistributedCache CreateCache(RedisConfig config)
        {
   var _client = new CSRedisClient($"{config.ConnectionString},prefix={config.InstanceName},testcluster=false");
returnreturn new CSRedisCache(_client); }

看上面代码,他们是自定义了一个Cache类,内部通过这个方法,来创建IDistributedCache 对象。而且项目中依赖Cache类的服务,非单例,而是Score,这样致使每次请求,都会构建一个

CSRedisClient实例,而且初始化CSRedisClient的地方,的确有卡顿。改造下吧!
  /// <summary>
        /// <![CDATA[推荐,添加Redis单例服务]]>
        /// </summary>
        /// <param name="services"></param>
        /// <param name="func"></param>
        public static void AddRedisCacheSingletonService(this IServiceCollection services, Func<RedisConfig> func)
        {
            var config = func.Invoke();
            services.TryAddSingleton(new RedisConfig
            {
                ConnectionString = config.ConnectionString,
                InstanceName = config.InstanceName
            });
            services.TryAddSingleton<ICache, Cache>();
        }

来一个单例,使用CSRedisClient时推荐使用单例模式,不够用,考虑对象池吧。有时候,不需要CSRedisClient的方法,由于Service构造函数注入,不得不构建了。这里可考虑使用懒加载Layz<T>,用法也很简单,只有使用的时候,才会被初始化,

也就是在首次使用Layz Vaue时,就初始化了。后面同一个Lazy实例,访问Value仍是同一个实例,相当于缓存了Value。

       /// <summary>
        /// 创建缓存(延迟加载)
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        private Lazy<IDistributedCache> CreateCache(RedisConfig config)
        {
            return
                new Lazy<IDistributedCache>(() =>
                {
                    _client = new CSRedisClient($"{config.ConnectionString},prefix={config.InstanceName},testcluster=false");
                    return new CSRedisCache(_client);
                });
        }

经过两个地方的调整,接口响应时间从3s+,降低到了平均267ms。

  好的东西,拿来就用的同时,有空请审视一下。

  至此,算是今天的随笔,算是告一段落。想来,已经快两年没有更新过博客了,到了饭点,各位干饭要紧!~