接着上一章节继续唠唠
本章主要说一下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代表redis库名,redisHost代表链接库地址,redisPass代表密码
三.初级测试
测试代码如下:
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)
这个问题的根本原因在于我们配置的redis默认等待时间
我现在用的是等待二十秒不行,如果改才600等待10分钟,你的timeout应该就不会出现了!(如有不对请斧正)
这一篇就这样吧,下一篇我会唠唠这个系统的权限管理模块的实现
- 下章管理系统模块实现
原文地址:https://www.cnblogs.com/gdsblog/p/10004615.html