1. 程式人生 > >[外包]!採用asp.net core 快速構建小型創業公司後臺管理系統(三)

[外包]!採用asp.net core 快速構建小型創業公司後臺管理系統(三)

接著上一章節繼續嘮嘮

本章主要說一下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應該就不會出現了!(如有不對請斧正)

  這一篇就這樣吧,下一篇我會嘮嘮這個系統的許可權管理模組的實現

  • 下章管理系統模組實現