[外包]!采用asp.net core 快速構建小型創業公司后台管理系統(三.Redis優化,StackExchange.Redis沒毛病)


本章主要說一下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代表用戶名,redisHost代表鏈接庫地址,redisPass代表密碼

  reisIndex代表庫名

三.初級測試

  測試代碼如下:

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)

   在看一下我異常處理中間件攔截下來的用Log4net記錄的日志

  

 

  這個問題的根本原因在於我們配置的redis默認等待時間,今天早上我修改了一些,斷定問題出現在連接時間上connectTime,sysncTimeOut

   

   //剛剛對這里又做了優化,徹底測出了timeout那個問題,另外配置完全放到了json里,通過強類型model,在拓展里配置

  

  redisOption修改成如下:

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; }

        /// <summary>
        /// 異步連接等待時間
        /// </summary>
        public int ConnectTimeout { get; set; } = 600;

        /// <summary>
        /// 同步連接等待時間
        /// </summary>
        public int SyncTimeout { get; set; } = 600;

        /// <summary>
        /// 最大連接數
        /// </summary>
        public int KeepAlive { get; set; } = 30;

        /// <summary>
        /// 連接重試次數
        /// </summary>
        public int ConnectRetry { get; set; } = 10;

        /// <summary>
        /// 獲取或設置是否應顯式通知連接/配置超時通過TimeoutException
        /// </summary>
        public bool AbortOnConnectFail { get; set; } = true;

        /// <summary>
        /// 是否允許管理員操作
        /// </summary>
        public bool AllowAdmin { get; set; } = true;
    }

  其余沒有什么大的變化!

 

  最后想說,StackExchange.Redis 這個包沒什么問題,我依然推薦用這個包,至少目前我真沒有發現無法解決的問題!!!

 

  我現在用的是等待二十秒不行,如果改才600等待10分鍾,你的timeout應該就不會出現了!(如有不對請斧正)

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

  • 下章管理系統模塊實現

 

 

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM