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

本章主要说一下Redis

  • Redis操作优化

一.基础类的配置工作

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

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

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

  实现代码如下:

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

        /// 
        ////// 
        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 = 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 系统配置
        /// 
        /// 获取系统配置
        /// 
        /// 
        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 获取缓存
        /// 
        /// 读取缓存
        /// 
        /// 
        /// 数据类型/NULL
        public static object Get(string key)
        {
            return Get<object>(key);
        }
        /// 
        /// 读取缓存
        /// 
        /// 泛型
        /// 
        /// 数据类型/NULL
        public static T Get(string key)
        {
            var value = _db.StringGet(key);
            return (value.IsNull ? default(T) : JsonTo(value).Value);
        }
        #endregion

        #region 异步获取缓存
        /// 
        /// 异步读取缓存
        /// 
        /// 
        /// object/NULL
        public static async Task<object> GetAsync(string key)
        {
            return await GetAsync<object>(key);
        }
        /// 
        /// 异步读取缓存
        /// 
        /// 泛型
        /// 
        /// 数据类型/NULL
        public static async Task GetAsync(string key)
        {
            var value = await _db.StringGetAsync(key);
            return (value.IsNull ? default(T) : JsonTo(value).Value);
        }
        #endregion

        #region 同步转异步添加[I/O密集]
        /// 
        /// 添加缓存
        /// 
        /// 
        /// 数据
        /// 是否永久保存[true:是,false:保存10分钟]
        public static bool Insert(string key, object data, bool never = false)
        {
            return InsertAsync(key, data, never).Result;
        }
        /// 
        /// 添加缓存
        /// 
        /// 泛型
        /// 
        /// 数据
        /// 是否永久保存[true:是,false:保存10分钟]
        /// 添加结果
        public static bool Insert(string key, T data, bool never = false)
        {
            return InsertAsync(key, data, never).Result;
        }
        /// 
        /// 添加缓存
        /// 
        /// 
        /// 数据
        /// 保存时间[单位:秒]
        /// 添加结果
        public static bool Insert(string key, object data, int time)
        {
            return InsertAsync(key, data, time).Result;
        }
        /// 
        /// 添加缓存
        /// 
        /// 泛型
        /// 
        /// 数据
        /// 保存时间[单位:秒]
        /// 添加结果
        public static bool Insert(string key, T data, int time)
        {
            return InsertAsync(key, data, time).Result;
        }
        /// 
        /// 添加缓存
        /// 
        /// 
        /// 数据
        /// 缓存时间
        /// 添加结果
        public static bool Insert(string key, object data, DateTime cachetime)
        {
            return InsertAsync(key, data, cachetime).Result;
        }
        /// 
        /// 添加缓存
        /// 
        /// 泛型
        /// 
        /// 数据
        /// 缓存时间
        /// 添加结果
        public static bool Insert(string key, T data, DateTime cachetime)
        {
            return InsertAsync(key, data, cachetime).Result;
        }
        #endregion

        #region 异步添加
        /// 
        /// 添加缓存[异步]
        /// 
        /// 
        /// 数据
        /// 是否永久保存[true:是,false:保存10分钟]
        /// 添加结果
        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))));
        }
        /// 
        /// 添加缓存[异步]
        /// 
        /// 泛型
        /// 
        /// 数据
        /// 是否永久保存[true:是,false:保存10分钟]
        /// 添加结果
        public static async Task<bool> InsertAsync(string key, T data, bool never = false)
        {
            return await _db.StringSetAsync(key, ToJson(data), (never ? null : new TimeSpan?(TimeSpan.FromSeconds(_DefulatTime))));
        }
        /// 
        /// 添加缓存[异步]
        /// 
        /// 
        /// 数据
        /// 保存时间[单位:秒]
        /// 添加结果
        public static async Task<bool> InsertAsync(string key, object data, int time)
        {
            return await _db.StringSetAsync(key, ToJson(data), new TimeSpan?(TimeSpan.FromSeconds(time)));
        }
        /// 
        /// 添加缓存[异步]
        /// 
        /// 泛型
        /// 
        /// 数据
        /// 保存时间[单位:秒]
        /// 添加结果
        public static async Task<bool> InsertAsync(string key, T data, int time)
        {
            return await _db.StringSetAsync(key, ToJson(data), new TimeSpan?(TimeSpan.FromSeconds(time)));
        }
        /// 
        /// 添加缓存[异步]
        /// 
        /// 
        /// 数据
        /// 缓存时间
        /// 添加结果
        public static async Task<bool> InsertAsync(string key, object data, DateTime cachetime)
        {
            return await _db.StringSetAsync(key, ToJson(data), new TimeSpan?(cachetime - DateTime.Now));
        }
        /// 
        /// 添加缓存[异步]
        /// 
        /// 泛型
        /// 
        /// 数据
        /// 缓存时间
        /// 添加结果
        public static async Task<bool> InsertAsync(string key, T data, DateTime cachetime)
        {
            return await _db.StringSetAsync(key, ToJson(data), new TimeSpan?(cachetime - DateTime.Now));
        }
        #endregion

        #region 验证缓存
        /// 
        /// 验证缓存是否存在
        /// 
        /// 
        /// 验证结果
        public static bool Exists(string key)
        {
            return _db.KeyExists(key);
        }
        #endregion

        #region 异步验证缓存
        /// 
        /// 验证缓存是否存在
        /// 
        /// 
        /// 验证结果
        public static async Task<bool> ExistsAsync(string key)
        {
            return await _db.KeyExistsAsync(key);
        }
        #endregion

        #region 移除缓存
        /// 
        /// 移除缓存
        /// 
        /// 
        /// 移除结果
        public static bool Remove(string key)
        {
            return _db.KeyDelete(key);
        }
        #endregion

        #region 异步移除缓存
        /// 
        /// 移除缓存
        /// 
        /// 
        /// 移除结果
        public static async Task<bool> RemoveAsync(string key)
        {
            return await _db.KeyDeleteAsync(key);
        }
        #endregion

        #region 队列发布
        /// 
        /// 队列发布
        /// 
        /// 通道名
        /// 数据
        /// 是否有消费者接收
        public static bool Publish(Models.RedisChannels Key, object data)
        {
            return _sub.Publish(Key.ToString(), ToJson(data)) > 0 ? true : false;
        }
        #endregion

        #region 队列接收
        /// 
        /// 注册通道并执行对应方法
        /// 
        /// 数据类型
        /// 通道名
        /// 方法
        public static void Subscribe(Models.RedisChannels Key, DoSub doSub) where T : class
        {
            var _subscribe = connection.GetSubscriber();
            _subscribe.Subscribe(Key.ToString(), delegate (RedisChannel channel, RedisValue message)
            {
                T t = Recieve(message);
                doSub(t);
            });
        }
        #endregion

        #region 退订队列通道
        /// 
        /// 退订队列通道
        /// 
        /// 通道名
        public static void UnSubscribe(Models.RedisChannels Key)
        {
            _sub.Unsubscribe(Key.ToString());
        }
        #endregion

        #region 数据转换
        /// 
        /// JSON转换配置文件
        /// 
        private static JsonSerializerSettings _jsoncfg = new JsonSerializerSettings
        {
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            NullValueHandling = NullValueHandling.Ignore,
            Formatting = Formatting.None
        };
        /// 
        /// 封装模型转换为字符串进行存储
        /// 
        /// 
        /// 
        private static string ToJson(object value)
        {
            return ToJson<object>(value);
        }
        /// 
        /// 封装模型转换为字符串进行存储
        /// 
        /// 泛型
        /// 
        /// 
        private static string ToJson(T value)
        {
            return JsonConvert.SerializeObject(new Models.RedisData
            {
                Value = value
            }, _jsoncfg);
        }
        /// 
        /// 缓存字符串转为封装模型
        /// 
        /// 
        /// 
        /// 
        private static Models.RedisData JsonTo(string value)
        {
            return JsonConvert.DeserializeObject>(value, _jsoncfg);
        }

        private static T Recieve(string cachevalue)
        {
            T result = default(T);
            bool flag = !string.IsNullOrWhiteSpace(cachevalue);
            if (flag)
            {
                var cacheObject = JsonConvert.DeserializeObject>(cachevalue, _jsoncfg);
                result = cacheObject.Value;
            }
            return result;
        }
        #endregion

        #region 方法委托
        /// 
        /// 委托执行方法
        /// 
        /// 
        public delegate void DoSub(object d);
        #endregion
    }

二.在starup里注入

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

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

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

  如图:

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

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

  这里我们通过上一章的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();
        }

  测试结果:

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

  result=true表示写入成功

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

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

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

 

四.redis高级测试

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

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

  如图:

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

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

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

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

  

/// 
        /// 注册通道并执行对应方法
        /// 
        /// 数据类型
        /// 
        /// 通道名
        /// 方法
        public static IServiceCollection Subscribe(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(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();
        }

  测试结果:

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

  日志上成功记录下来,也就是说,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();
        }

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

 

  

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

 

 

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

  如图:

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

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

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

 

  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记录的日志

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

 

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

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

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

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

  redisOption修改成如下:

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

        /// 
        ////// 
        public int RedisIndex { get; set; }

        /// 
        /// 异步连接等待时间
        /// 
        public int ConnectTimeout { get; set; } = 600;

        /// 
        /// 同步连接等待时间
        /// 
        public int SyncTimeout { get; set; } = 600;

        /// 
        /// 最大连接数
        /// 
        public int KeepAlive { get; set; } = 30;

        /// 
        /// 连接重试次数
        /// 
        public int ConnectRetry { get; set; } = 10;

        /// 
        /// 获取或设置是否应显式通知连接/配置超时通过TimeoutException
        /// 
        public bool AbortOnConnectFail { get; set; } = true;

        /// 
        /// 是否允许管理员操作
        /// 
        public bool AllowAdmin { get; set; } = true;
    }

  其余没有什么大的变化!

 

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

 

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

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

  • 下章管理系统模块实现

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

 

 

 

 

 

  

 

转载于:https://www.cnblogs.com/gdsblog/p/10004615.html

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