• [外包]!采用asp.net core 快速构建小型创业公司后台管理系统(三.Redis优化,StackExchange.Redis没毛病)


    本章主要说一下Redis

    • Redis操作优化

    一.基础类的配置工作

      1.我想信许多人(许多neter人)操作redis依然用的是StackExchange.Redis,这个neget包,并没有用国内现在一些大佬们推出了包

      

      RedisOptions主要是redis连接的一个配置类

      实现代码如下:

    public class RedisOptions
        {
            /// <summary>
            /// 数据库地址
            /// </summary>
            public string RedisHost { get; set; }
            /// <summary>
            /// 数据库用户名
            /// </summary>
            public string RedisName { get; set; }
            /// <summary>
            /// 数据库密码
            /// </summary>
            public string RedisPass { get; set; }
    
            /// <summary>
            ////// </summary>
            public int RedisIndex { get; set; }
        }

      RedisServiceExtensions,算是redis操作的核心类,主要封装了redis的crud以及sub,pub

    public static class RedisServiceExtensions
        {
            #region 初始化参数
            private static readonly int _DefulatTime = 600; //默认有效期
            private static RedisOptions config;
            private static ConnectionMultiplexer connection;
            private static IDatabase _db;
            private static ISubscriber _sub;
            #endregion
            public static IServiceCollection AddRedisCacheService(
                this IServiceCollection serviceCollection,
                Func<RedisOptions, RedisOptions> redisOptions = null
                )
            {
                var _redisOptions = new RedisOptions();
                _redisOptions = redisOptions?.Invoke(_redisOptions) ?? _redisOptions;
                config = _redisOptions;
                connection = ConnectionMultiplexer.Connect(GetSystemOptions());
                _db = connection.GetDatabase(config.RedisIndex);
                _sub = connection.GetSubscriber();
                return serviceCollection;
            }
            
            #region 系统配置
            /// <summary>
            /// 获取系统配置
            /// </summary>
            /// <returns></returns>
            private static ConfigurationOptions GetSystemOptions()
            {
                var options = new ConfigurationOptions
                {
                    AbortOnConnectFail = false,
                    AllowAdmin = true,
                    ConnectRetry = 10,
                    ConnectTimeout = 5000,
                    KeepAlive = 30,
                    SyncTimeout = 5000,
                    EndPoints = { config.RedisHost },
                    ServiceName = config.RedisName,
                };
                if (!string.IsNullOrWhiteSpace(config.RedisPass))
                {
                    options.Password = config.RedisPass;
                }
                return options;
            }
            #endregion
    
            //============
            #region 获取缓存
            /// <summary>
            /// 读取缓存
            /// </summary>
            /// <param name="key"></param>
            /// <returns>数据类型/NULL</returns>
            public static object Get(string key)
            {
                return Get<object>(key);
            }
            /// <summary>
            /// 读取缓存
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <returns>数据类型/NULL</returns>
            public static T Get<T>(string key)
            {
                var value = _db.StringGet(key);
                return (value.IsNull ? default(T) : JsonTo<T>(value).Value);
            }
            #endregion
    
            #region 异步获取缓存
            /// <summary>
            /// 异步读取缓存
            /// </summary>
            /// <param name="key"></param>
            /// <returns>object/NULL</returns>
            public static async Task<object> GetAsync(string key)
            {
                return await GetAsync<object>(key);
            }
            /// <summary>
            /// 异步读取缓存
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <returns>数据类型/NULL</returns>
            public static async Task<T> GetAsync<T>(string key)
            {
                var value = await _db.StringGetAsync(key);
                return (value.IsNull ? default(T) : JsonTo<T>(value).Value);
            }
            #endregion
    
            #region 同步转异步添加[I/O密集]
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="never">是否永久保存[true:是,false:保存10分钟]</param>
            public static bool Insert(string key, object data, bool never = false)
            {
                return InsertAsync(key, data, never).Result;
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="never">是否永久保存[true:是,false:保存10分钟]</param>
            /// <returns>添加结果</returns>
            public static bool Insert<T>(string key, T data, bool never = false)
            {
                return InsertAsync<T>(key, data, never).Result;
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="time">保存时间[单位:秒]</param>
            /// <returns>添加结果</returns>
            public static bool Insert(string key, object data, int time)
            {
                return InsertAsync(key, data, time).Result;
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="time">保存时间[单位:秒]</param>
            /// <returns>添加结果</returns>
            public static bool Insert<T>(string key, T data, int time)
            {
                return InsertAsync<T>(key, data, time).Result;
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="cachetime">缓存时间</param>
            /// <returns>添加结果</returns>
            public static bool Insert(string key, object data, DateTime cachetime)
            {
                return InsertAsync(key, data, cachetime).Result;
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="cachetime">缓存时间</param>
            /// <returns>添加结果</returns>
            public static bool Insert<T>(string key, T data, DateTime cachetime)
            {
                return InsertAsync<T>(key, data, cachetime).Result;
            }
            #endregion
    
            #region 异步添加
            /// <summary>
            /// 添加缓存[异步]
            /// </summary>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="never">是否永久保存[true:是,false:保存10分钟]</param>
            /// <returns>添加结果</returns>
            public static async Task<bool> InsertAsync(string key, object data, bool never = false)
            {
                return await _db.StringSetAsync(key, ToJson(data), (never ? null : new TimeSpan?(TimeSpan.FromSeconds(_DefulatTime))));
            }
            /// <summary>
            /// 添加缓存[异步]
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="never">是否永久保存[true:是,false:保存10分钟]</param>
            /// <returns>添加结果</returns>
            public static async Task<bool> InsertAsync<T>(string key, T data, bool never = false)
            {
                return await _db.StringSetAsync(key, ToJson<T>(data), (never ? null : new TimeSpan?(TimeSpan.FromSeconds(_DefulatTime))));
            }
            /// <summary>
            /// 添加缓存[异步]
            /// </summary>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="time">保存时间[单位:秒]</param>
            /// <returns>添加结果</returns>
            public static async Task<bool> InsertAsync(string key, object data, int time)
            {
                return await _db.StringSetAsync(key, ToJson(data), new TimeSpan?(TimeSpan.FromSeconds(time)));
            }
            /// <summary>
            /// 添加缓存[异步]
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="time">保存时间[单位:秒]</param>
            /// <returns>添加结果</returns>
            public static async Task<bool> InsertAsync<T>(string key, T data, int time)
            {
                return await _db.StringSetAsync(key, ToJson<T>(data), new TimeSpan?(TimeSpan.FromSeconds(time)));
            }
            /// <summary>
            /// 添加缓存[异步]
            /// </summary>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="cachetime">缓存时间</param>
            /// <returns>添加结果</returns>
            public static async Task<bool> InsertAsync(string key, object data, DateTime cachetime)
            {
                return await _db.StringSetAsync(key, ToJson(data), new TimeSpan?(cachetime - DateTime.Now));
            }
            /// <summary>
            /// 添加缓存[异步]
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="key"></param>
            /// <param name="data">数据</param>
            /// <param name="cachetime">缓存时间</param>
            /// <returns>添加结果</returns>
            public static async Task<bool> InsertAsync<T>(string key, T data, DateTime cachetime)
            {
                return await _db.StringSetAsync(key, ToJson<T>(data), new TimeSpan?(cachetime - DateTime.Now));
            }
            #endregion
    
            #region 验证缓存
            /// <summary>
            /// 验证缓存是否存在
            /// </summary>
            /// <param name="key"></param>
            /// <returns>验证结果</returns>
            public static bool Exists(string key)
            {
                return _db.KeyExists(key);
            }
            #endregion
    
            #region 异步验证缓存
            /// <summary>
            /// 验证缓存是否存在
            /// </summary>
            /// <param name="key"></param>
            /// <returns>验证结果</returns>
            public static async Task<bool> ExistsAsync(string key)
            {
                return await _db.KeyExistsAsync(key);
            }
            #endregion
    
            #region 移除缓存
            /// <summary>
            /// 移除缓存
            /// </summary>
            /// <param name="key"></param>
            /// <returns>移除结果</returns>
            public static bool Remove(string key)
            {
                return _db.KeyDelete(key);
            }
            #endregion
    
            #region 异步移除缓存
            /// <summary>
            /// 移除缓存
            /// </summary>
            /// <param name="key"></param>
            /// <returns>移除结果</returns>
            public static async Task<bool> RemoveAsync(string key)
            {
                return await _db.KeyDeleteAsync(key);
            }
            #endregion
    
            #region 队列发布
            /// <summary>
            /// 队列发布
            /// </summary>
            /// <param name="Key">通道名</param>
            /// <param name="data">数据</param>
            /// <returns>是否有消费者接收</returns>
            public static bool Publish(Models.RedisChannels Key, object data)
            {
                return _sub.Publish(Key.ToString(), ToJson(data)) > 0 ? true : false;
            }
            #endregion
    
            #region 队列接收
            /// <summary>
            /// 注册通道并执行对应方法
            /// </summary>
            /// <typeparam name="T">数据类型</typeparam>
            /// <param name="Key">通道名</param>
            /// <param name="doSub">方法</param>
            public static void Subscribe<T>(Models.RedisChannels Key, DoSub doSub) where T : class
            {
                var _subscribe = connection.GetSubscriber();
                _subscribe.Subscribe(Key.ToString(), delegate (RedisChannel channel, RedisValue message)
                {
                    T t = Recieve<T>(message);
                    doSub(t);
                });
            }
            #endregion
    
            #region 退订队列通道
            /// <summary>
            /// 退订队列通道
            /// </summary>
            /// <param name="Key">通道名</param>
            public static void UnSubscribe(Models.RedisChannels Key)
            {
                _sub.Unsubscribe(Key.ToString());
            }
            #endregion
    
            #region 数据转换
            /// <summary>
            /// JSON转换配置文件
            /// </summary>
            private static JsonSerializerSettings _jsoncfg = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                NullValueHandling = NullValueHandling.Ignore,
                Formatting = Formatting.None
            };
            /// <summary>
            /// 封装模型转换为字符串进行存储
            /// </summary>
            /// <param name="value"></param>
            /// <returns></returns>
            private static string ToJson(object value)
            {
                return ToJson<object>(value);
            }
            /// <summary>
            /// 封装模型转换为字符串进行存储
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="value"></param>
            /// <returns></returns>
            private static string ToJson<T>(T value)
            {
                return JsonConvert.SerializeObject(new Models.RedisData<T>
                {
                    Value = value
                }, _jsoncfg);
            }
            /// <summary>
            /// 缓存字符串转为封装模型
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="value"></param>
            /// <returns></returns>
            private static Models.RedisData<T> JsonTo<T>(string value)
            {
                return JsonConvert.DeserializeObject<Models.RedisData<T>>(value, _jsoncfg);
            }
    
            private static T Recieve<T>(string cachevalue)
            {
                T result = default(T);
                bool flag = !string.IsNullOrWhiteSpace(cachevalue);
                if (flag)
                {
                    var cacheObject = JsonConvert.DeserializeObject<Models.RedisData<T>>(cachevalue, _jsoncfg);
                    result = cacheObject.Value;
                }
                return result;
            }
            #endregion
    
            #region 方法委托
            /// <summary>
            /// 委托执行方法
            /// </summary>
            /// <param name="d"></param>
            public delegate void DoSub(object d);
            #endregion
        }

    二.在starup里注入

      

      AddRedisCacheService是我在RedisServiceExtensions里放的拓展方法,这里用来注入redis的配置,RedisOptionKey是我在预编译变量里放置的key,

      对应appsetting.json里配置文件的key

      如图:

      

      

      这里我们通过上一章的ConfigLocator很轻松的就拿到了redisOptions强类型的值

      到这里我们基本配置就完成,再说明一下,redisconfig的配置,redisName代表用户名,redisHost代表链接库地址,redisPass代表密码

      reisIndex代表库名

    三.初级测试

      测试代码如下:

    public IActionResult Index()
            {
                var key = "Test_Redis_Key";
    
                var result= RedisServiceExtensions.Insert(key,"good");
    
                var value = RedisServiceExtensions.Get(key);
                return View();
            }

      测试结果:

      

      result=true表示写入成功

      

      value=good恰好是我们刚才写的good

      初级对reids的测试就是这么简单,客户端连接数据库我就不演示了

    四.redis高级测试

      高级测试我主要测试pub和sub并且要给大家演示出timeout那个问题,我争取将其复现了!

      首先强调一点pub和sub是有通道的,通道大家不陌生吧!

      如图:

      

      程序启动,我们先订阅一个通道,本此测试我订阅两个通道,根据有说服力!

      subscribe是一个泛型方法,泛型约束的是执行方法的参数类型,有两个入参,一个是通道名称,一个是要执行的委托方法

      代码如下:注意我这里将其做成拓展方法,并且开另一个线程执行

      

    /// <summary>
            /// 注册通道并执行对应方法
            /// </summary>
            /// <typeparam name="T">数据类型</typeparam>
            /// <param name="serviceCollection"></param>
            /// <param name="Key">通道名</param>
            /// <param name="doSub">方法</param>
            public static IServiceCollection Subscribe<T>(this IServiceCollection serviceCollection,Models.RedisChannels Key, DoSub doSub) where T : class
            {
                Task.Run(() =>
                {
                    var _subscribe = connection.GetSubscriber();
                    _subscribe.Subscribe(Key.ToString(), delegate (RedisChannel channel, RedisValue message)
                    {
                        T t = Recieve<T>(message);
                        doSub(t);
                    });
                });
                return serviceCollection;
            }

      RedisService.SubscribeDoSomething,RedisService.MemberChannel_SubscribeDoSomething是两个委托方法,我给第一个通道pub,他就执行一次SubscribeDoSomething

      给第二个通道pub,他就执行一次MemberChannel_SubscribeDoSomething

      这两个方法实现如下:

      

    public class RedisService
        {
            public static void SubscribeDoSomething(object query)
            {
                int num = 0;
                Log4Net.Info($"TestPubSub_通道订阅_{num}");
                num += 1;
            }
    
            public static void MemberChannel_SubscribeDoSomething(object query)
            {
                query= query as string;
                int num = 0;
                Log4Net.Info($"MemberChannel_SubscribeDoSomething_{query}_{num}");
                num += 1;
            }
        }

      接下来我还是在控制器里调用,进行pub  

    public IActionResult Index()
            {
                //发布TestPubSub
                var result = RedisServiceExtensions.Publish( Infrastructrue.Caches.Redis.Models.RedisChannels.TestPubSub,null);
    
                //发布MemberRegister
                var result2 = RedisServiceExtensions.Publish(Infrastructrue.Caches.Redis.Models.RedisChannels.MemberRegister, "哥就是哥_不一样的烟火...");
    
                return View();
            }

      测试结果:

      

      日志上成功记录下来,也就是说,pub出去的东西,sub到,然后执行写了日志!

      接下来我让控制器循环执行100次pub,我们让第二个通道的pub100次,第一个通道就pub一次

      代码如下:

    public IActionResult Index()
            {
                //发布TestPubSub
                var result = RedisServiceExtensions.Publish(Infrastructrue.Caches.Redis.Models.RedisChannels.TestPubSub, null);
    
                for (int i = 0; i < 100; i++)
                {
                    //发布MemberRegister
                    var result2 = RedisServiceExtensions.Publish(Infrastructrue.Caches.Redis.Models.RedisChannels.MemberRegister, "哥就是哥_不一样的烟火...");
    
                }
                return View();
            }

      

      哈哈,太happy了,一次把我要说的那个问题------------timeout的问题测出来了!

      如图:

      

      详细信息如下:遇到的肯定是这个问题想都不用想

      

      Timeout performing PUBLISH MemberRegister (5000ms), inst: 24, qs: 0, in: 0, serverEndpoint: 39.107.202.142:6379, mgr: 9 of 10 available, clientName: GY, IOCP: (Busy=0,Free=1000,Min=4,Max=1000), WORKER: (Busy=5,Free=32762,Min=4,Max=32767), v: 2.0.513.63329 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)

       在看一下我异常处理中间件拦截下来的用Log4net记录的日志

      

      这个问题的根本原因在于我们配置的redis默认等待时间,今天早上我修改了一些,断定问题出现在连接时间上connectTime,sysncTimeOut

       

       //刚刚对这里又做了优化,彻底测出了timeout那个问题,另外配置完全放到了json里,通过强类型model,在拓展里配置

      

      redisOption修改成如下:

    public class RedisOptions
        {
            /// <summary>
            /// 数据库地址
            /// </summary>
            public string RedisHost { get; set; }
            /// <summary>
            /// 数据库用户名
            /// </summary>
            public string RedisName { get; set; }
            /// <summary>
            /// 数据库密码
            /// </summary>
            public string RedisPass { get; set; }
    
            /// <summary>
            ////// </summary>
            public int RedisIndex { get; set; }
    
            /// <summary>
            /// 异步连接等待时间
            /// </summary>
            public int ConnectTimeout { get; set; } = 600;
    
            /// <summary>
            /// 同步连接等待时间
            /// </summary>
            public int SyncTimeout { get; set; } = 600;
    
            /// <summary>
            /// 最大连接数
            /// </summary>
            public int KeepAlive { get; set; } = 30;
    
            /// <summary>
            /// 连接重试次数
            /// </summary>
            public int ConnectRetry { get; set; } = 10;
    
            /// <summary>
            /// 获取或设置是否应显式通知连接/配置超时通过TimeoutException
            /// </summary>
            public bool AbortOnConnectFail { get; set; } = true;
    
            /// <summary>
            /// 是否允许管理员操作
            /// </summary>
            public bool AllowAdmin { get; set; } = true;
        }

      其余没有什么大的变化!

      最后想说,StackExchange.Redis 这个包没什么问题,我依然推荐用这个包,至少目前我真没有发现无法解决的问题!!!

      我现在用的是等待二十秒不行,如果改才600等待10分钟,你的timeout应该就不会出现了!(如有不对请斧正)

      这一篇就这样吧,下一篇我会唠唠这个系统的权限管理模块的实现

    • 下章管理系统模块实现

      

  • 相关阅读:
    javascript 原型和构造函数
    react native与原生的交互
    项目中git的用法
    web页面的回流,认识与避免
    js 中的算法题,那些经常看到的
    js中this的四种调用模式
    JS面向对象的几种写法
    模块化加载require.js
    es6新语法
    vue组件化开发
  • 原文地址:https://www.cnblogs.com/gdsblog/p/10004615.html
Copyright © 2020-2023  润新知