菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
126
0

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

原创
05/13 14:22
阅读数 13464

本章主要说一下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应该就不会出现了!(如有不对请斧正)

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

  • 下章管理系统模块实现

 

 

 

 

 

  

 

发表评论

0/200
126 点赞
0 评论
收藏