[外包]!採用asp.net core 快速構建小型創業公司後臺管理系統(三)
接著上一章節繼續嘮嘮
本章主要說一下Redis
- Redis操作優化
一.基礎類的配置工作
1.我想信許多人(許多neter人)操作redis依然用的是StackExchange.Redis,這個neget包,並沒有用國內現在一些大佬們推出了包
RedisOptions主要是redis連線的一個配置類
實現程式碼如下:
public class RedisOptions { /// <summary> /// 資料庫地址 /// </summary> publicstring 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; //預設有效期 privatestatic 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應該就不會出現了!(如有不對請斧正)
這一篇就這樣吧,下一篇我會嘮嘮這個系統的許可權管理模組的實現
- 下章管理系統模組實現