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

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

bus issues model nco mage oop 推出 兩個 options

接著上一章節繼續嘮嘮

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

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

  • 下章管理系統模塊實現

技術分享圖片

  

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