快取管理之MemoryCache與Redis的使用

  • 2019 年 11 月 12 日
  • 筆記

一、.MemoryCache介紹

              MemoryCache是.Net Framework 4.0開始提供的記憶體快取類,使用該類型可以方便的在程式內部快取數據並對於數據的有效性進行方便的管理, 它通過在記憶體中快取數據和對象來減少讀取資料庫的次數,從而減輕資料庫負載,加快數據讀取速度,提升系統的性能。

二、Redis介紹

              Redis是一個開源的key-value存儲系統,它支援的數據類型包括string(字元串)、 list(鏈表)、set(集合)、zset(sorted set –有序集合)和hashs(哈希)數據類型的相關操作

三、MemoryCache與Redis的區別

       1、性能方面:Redis 只能使用單核(如果確實需要充分使用多核cpu的能力,那麼需要在單台伺服器上運行多個redis實例(主從部署/集群化部署),並將每個redis實例和cpu內核進行綁定),而 MemoryCache可以使用多核,所以每一個核上Redis在存儲小數據時比Memcached性能更高。而存儲大數據時,Memcached性能要高於Redis。

       2、記憶體管理方面: MemoryCache使用預分配的記憶體池的方式,使用slab和大小不同的chunk來管理記憶體,Item根據大小選擇合適的chunk存儲,記憶體池的方式可以省去申請/釋放記憶體的開銷,並且能 減小記憶體碎片產生,但這種方式也會帶來一定程度上的空間浪費,並且在記憶體仍然有很大空間時,新的數據也可能會被剔除; Redis使用現場申請記憶體的方式來存儲數據,並且很少使用free-list等方式來優化記憶體分配,會在一定程度上存在記憶體碎片,在Redis中,並不是所有的數據都一直存儲在記憶體中的,當物理記憶體用完時,Redis可以將一些很久沒用到的value交換到磁碟。

      3、數據持久化支援:Redis雖然是基於記憶體的存儲系統,但是它本身是支援記憶體數據的持久化的,而且提供兩種主要的持久化策略:RDB快照和AOF日誌。而MemoryCache是不支援數據持久化操作的。

四、本系統中使用MemoryCache和Redis

      目標:   1、MemoryCache和Redis使用無縫切換,統一介面,通過配置選擇使用MemoryCache還是Redis

                 2、使用Redis時,減少對Redis的讀取(HttpContextAccessor配合Redis使用)

      實現:

                 MemoryCache我們採用EasyCaching(可以從git上獲取:https://github.com/dotnetcore/EasyCaching),由於本身提供的介面不滿足需求,所以我們直接下載到本地,將EasyCaching.Core和EasyCaching.InMemory添加到項目中,如圖所示:

                

在IEasyCachingProvider添加介面   

/// <summary>          /// Removes cached item by cachekey's contain.          /// </summary>          /// <param name="contain"></param>          void RemoveByContain(string contain);

View Code

在EasyCachingAbstractProvider.cs添加程式碼

 public abstract void BaseRemoveByContain(string contain); 

        public void RemoveByContain(string contain)          {              var operationId = s_diagnosticListener.WriteRemoveCacheBefore(new BeforeRemoveRequestEventData(CachingProviderType.ToString(), Name, nameof(RemoveByPrefix), new[] { contain }));              Exception e = null;              try              {                  BaseRemoveByContain(contain);              }              catch (Exception ex)              {                  e = ex;                  throw;              }              finally              {                  if (e != null)                  {                      s_diagnosticListener.WriteRemoveCacheError(operationId, e);                  }                  else                  {                      s_diagnosticListener.WriteRemoveCacheAfter(operationId);                  }              }          }

View Code

在DefaultInMemoryCachingProvider.Async.cs中添加程式碼

        public override void BaseRemoveByContain(string contain)          {              ArgumentCheck.NotNullOrWhiteSpace(contain, nameof(contain));                var count = _cache.RemoveByContain(contain);                if (_options.EnableLogging)                  _logger?.LogInformation($"RemoveByContain : contain = {contain} , count = {count}");          }

View Code

在IInMemoryCaching.cs中添加介面

 int RemoveByContain(string contain); 

在InMemoryCaching.cs中實現介面

   public int RemoveByContain(string contain)          {              var keysToRemove = _memory.Keys.Where(x => x.Contains(contain)).ToList();              return RemoveAll(keysToRemove);          }

View Code

MemoryCache介面實現:MemoryCacheManager

using EasyCaching.Core;  using System;  using System.Threading.Tasks;    namespace Tools.Cache  {        /// <summary>      ///記憶體管理      /// </summary>      public partial class MemoryCacheManager : ILocker, IStaticCacheManager      {          #region Fields            private readonly IEasyCachingProvider _provider;            #endregion            #region Ctor            public MemoryCacheManager(IEasyCachingProvider provider)          {                _provider = provider;          }            #endregion            #region Methods            /// <summary>          ///通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">,如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          public T Get<T>(string key, Func<T> acquire, int? cacheTime = null)          {              if (cacheTime <= 0)                  return acquire();                return _provider.Get(key, acquire, TimeSpan.FromMinutes(cacheTime ?? CachingDefaults.CacheTime)).Value;            }          /// <summary>          /// 通過指定Key獲取快取數據,不存在則返回Null          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <returns></returns>          public T Get<T>(string key)          {              return _provider.Get<T>(key).Value;          }          /// <summary>          ///通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">,如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          public async Task<T> GetAsync<T>(string key, Func<Task<T>> acquire, int? cacheTime = null)          {              if (cacheTime <= 0)                  return await acquire();                var t = await _provider.GetAsync(key, acquire, TimeSpan.FromMinutes(cacheTime ?? CachingDefaults.CacheTime));              return t.Value;          }            /// <summary>          /// 設置快取          /// </summary>          /// <param name="key">Key</param>          /// <param name="data">Value</param>          /// <param name="cacheTime">快取時間(分鐘)</param>          public void Set(string key, object data, int cacheTime)          {              if (cacheTime <= 0)                  return;                _provider.Set(key, data, TimeSpan.FromMinutes(cacheTime));          }            /// <summary>          /// 判斷Key是否設置快取          /// </summary>          /// <param name="key">Key</param>          /// <returns>True表示存在;false則不存在</returns>          public bool IsSet(string key)          {              return _provider.Exists(key);          }            /// <summary>          /// 執行某些操作使用獨佔鎖          /// </summary>          /// <param name="resource">獨佔鎖的Key</param>          /// <param name="expirationTime">鎖自動過期的時間</param>          /// <param name="action">執行的操作</param>          /// <returns>如果獲取了鎖並執行了操作,則為true;否則為false</returns>          public bool PerformActionWithLock(string key, TimeSpan expirationTime, Action action)          {              if (_provider.Exists(key))                  return false;                try              {                  _provider.Set(key, key, expirationTime);                    action();                    return true;              }              finally              {                    Remove(key);              }          }            /// <summary>          ///通過Key刪除快取數據          /// </summary>          /// <param name="key">Key</param>          public void Remove(string key)          {              _provider.Remove(key);          }            /// <summary>          /// 刪除以prefix開頭的快取數據          /// </summary>          /// <param name="prefix">prefix開頭</param>          public void RemoveByPrefix(string prefix)          {              _provider.RemoveByPrefix(prefix);          }          /// <summary>          /// 刪除所有包含字元串的快取          /// </summary>          /// <param name="contain">包含的字元串</param>          public void RemoveByContain(string contain)          {              _provider.RemoveByContain(contain);          }          /// <summary>          /// 刪除所有的快取          /// </summary>          public void Clear()          {              _provider.Flush();          }            public virtual void Dispose()          {          }            #endregion      }  }

View Code

Redis實現:

CachingDefaults

using System;  using System.Collections.Generic;  using System.Text;    namespace Tools.Cache  {        public static partial class CachingDefaults      {          /// <summary>          /// 快取默認過期時間          /// </summary>          public static int CacheTime => 60;            /// <summary>          /// 獲取用於保護Key列表存儲到redis的Key(與啟用persistDataProtectionKeysRedis選項一起使用)          /// </summary>          public static string RedisDataProtectionKey => "API.DataProtectionKeys";      }  }

View Code

ILocker

using System;  using System.Collections.Generic;  using System.Text;    namespace Tools.Cache  {      public interface ILocker      {          /// <summary>          /// 執行某些操作使用獨佔鎖          /// </summary>          /// <param name="resource">獨佔鎖的Key</param>          /// <param name="expirationTime">鎖自動過期的時間</param>          /// <param name="action">執行的操作</param>          /// <returns>如果獲取了鎖並執行了操作,則為true;否則為false</returns>          bool PerformActionWithLock(string resource, TimeSpan expirationTime, Action action);      }  }

View Code

ICacheManager

using System;  using System.Collections.Generic;  using System.Text;    namespace Tools.Cache  {      /// <summary>      ///快取介面      /// </summary>      public interface ICacheManager : IDisposable      {          /// <summary>          ///通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">,如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          T Get<T>(string key, Func<T> acquire, int? cacheTime = null);          /// <summary>          /// 通過Key獲取指定快取,如果不存在則返回null          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <returns></returns>          T Get<T>(string key);            /// <summary>          /// 設置快取          /// </summary>          /// <param name="key">Key</param>          /// <param name="data">Value</param>          /// <param name="cacheTime">快取時間(分鐘)</param>          void Set(string key, object data, int cacheTime);            /// <summary>          /// 判斷Key是否設置快取          /// </summary>          /// <param name="key">Keym</param>          /// <returns>True表示存在;false則不存在</returns>          bool IsSet(string key);            /// <summary>          ///通過Key刪除快取數據          /// </summary>          /// <param name="key">Key</param>          void Remove(string key);            /// <summary>          /// 刪除以prefix開頭的快取數據          /// </summary>          /// <param name="prefix">prefix開頭的字元串</param>          void RemoveByPrefix(string prefix);            /// <summary>          /// 刪除包含字元串的快取          /// </summary>          /// <param name="contain">包含的字元串</param>          void RemoveByContain(string contain);            /// <summary>          /// 刪除所有的快取          /// </summary>          void Clear();      }  }

View Code

PerRequestCacheManager

using Microsoft.AspNetCore.Http;  using System;  using System.Collections.Generic;  using System.Linq;  using System.Text;  using System.Text.RegularExpressions;  using System.Threading;  using Tools.ComponentModel;    namespace Tools.Cache  {      /// <summary>      /// HTTP請求期間用於快取的管理器(短期快取)      /// </summary>      public partial class PerRequestCacheManager : ICacheManager      {          #region Ctor            public PerRequestCacheManager(IHttpContextAccessor httpContextAccessor)          {              _httpContextAccessor = httpContextAccessor;                _locker = new ReaderWriterLockSlim();          }            #endregion            #region Utilities            /// <summary>          ///獲取請求範圍內共享數據的key/value集合          /// </summary>          protected virtual IDictionary<object, object> GetItems()          {              return _httpContextAccessor.HttpContext?.Items;          }            #endregion            #region Fields            private readonly IHttpContextAccessor _httpContextAccessor;          private readonly ReaderWriterLockSlim _locker;            #endregion            #region Methods            /// <summary>          /// 通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          public virtual T Get<T>(string key, Func<T> acquire, int? cacheTime = null)          {              IDictionary<object, object> items;                using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read))              {                  items = GetItems();                  if (items == null)                      return acquire();                    //i如果快取存在,返回快取數據                  if (items[key] != null)                      return (T)items[key];              }                //或者通過方法創建              var result = acquire();                if (result == null || (cacheTime ?? CachingDefaults.CacheTime) <= 0)                  return result;                //設置快取(如果定義了快取時間)              using (new ReaderWriteLockDisposable(_locker))              {                  items[key] = result;              }                return result;          }          public T Get<T>(string key)          {              IDictionary<object, object> items;                using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read))              {                  items = GetItems();                    //i如果快取存在,返回快取數據                  if (items[key] != null)                      return (T)items[key];              }              return default(T);//沒有則返回默認值Null          }            /// <summary>          /// 設置快取          /// </summary>          /// <param name="key">Key</param>          /// <param name="data">Value</param>          /// <param name="cacheTime">快取時間(分鐘)</param>          public virtual void Set(string key, object data, int cacheTime)          {              if (data == null)                  return;                using (new ReaderWriteLockDisposable(_locker))              {                  var items = GetItems();                  if (items == null)                      return;                    items[key] = data;              }          }            /// <summary>          /// 判斷Key是否設置快取          /// </summary>          /// <param name="key">Key</param>          /// <returns>True表示存在;false則不存在</returns>          public virtual bool IsSet(string key)          {              using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read))              {                  var items = GetItems();                  return items?[key] != null;              }          }            /// <summary>          /// 通過Key刪除快取數據          /// </summary>          /// <param name="key">Key</param>          public virtual void Remove(string key)          {              using (new ReaderWriteLockDisposable(_locker))              {                  var items = GetItems();                  items?.Remove(key);              }          }            /// <summary>          /// 刪除以prefix開頭的快取數據          /// </summary>          /// <param name="prefix">prefix開頭</param>          public virtual void RemoveByPrefix(string prefix)          {              using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.UpgradeableRead))              {                  var items = GetItems();                  if (items == null)                      return;                    //匹配prefix                  var regex = new Regex(prefix,                      RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);                  var matchesKeys = items.Keys.Select(p => p.ToString()).Where(key => regex.IsMatch(key)).ToList();                    if (!matchesKeys.Any())                      return;                    using (new ReaderWriteLockDisposable(_locker))                  {                      //刪除快取                      foreach (var key in matchesKeys)                      {                          items.Remove(key);                      }                  }              }          }          /// <summary>          /// 刪除所有包含字元串的快取          /// </summary>          /// <param name="contain">包含的字元串</param>          public void RemoveByContain(string contain)          {              using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.UpgradeableRead))              {                  var items = GetItems();                  if (items == null)                      return;                    List<string> matchesKeys = new List<string>();                  var data = items.Keys.Select(p => p.ToString()).ToList();                    foreach(var item in data)                  {                      if(item.Contains(contain))                      {                          matchesKeys.Add(item);                      }                  }                    if (!matchesKeys.Any())                      return;                    using (new ReaderWriteLockDisposable(_locker))                  {                      //刪除快取                      foreach (var key in matchesKeys)                      {                          items.Remove(key);                      }                  }              }          }          /// <summary>          /// 清除所有快取          /// </summary>          public virtual void Clear()          {              using (new ReaderWriteLockDisposable(_locker))              {                  var items = GetItems();                  items?.Clear();              }          }              public virtual void Dispose()          {            }                #endregion      }  }

View Code

Redis介面實現:RedisCacheManager

using EasyCaching.Core.Serialization;  using Newtonsoft.Json;  using StackExchange.Redis;  using System;  using System.Collections.Generic;  using System.IO;  using System.Linq;  using System.Net;  using System.Runtime.Serialization.Formatters.Binary;  using System.Text;  using System.Threading.Tasks;  using Tools.Configuration;  using Tools.Redis;    namespace Tools.Cache  {      /// <summary>      /// Redis快取管理      /// </summary>      public partial class RedisCacheManager : IStaticCacheManager      {          #region Fields          private readonly ICacheManager _perRequestCacheManager;          private readonly IRedisConnectionWrapper _connectionWrapper;          private readonly IDatabase _db;            #endregion            #region Ctor            public RedisCacheManager(ICacheManager perRequestCacheManager,              IRedisConnectionWrapper connectionWrapper,              StartupConfig config)          {              if (string.IsNullOrEmpty(config.RedisConnectionString))                  throw new Exception("Redis 連接字元串為空!");                _perRequestCacheManager = perRequestCacheManager;              _connectionWrapper = connectionWrapper;              _db = _connectionWrapper.GetDatabase(config.RedisDatabaseId ?? (int)RedisDatabaseNumber.Cache);          }            #endregion            #region Utilities            protected byte[] Serialize<T>(T value)          {              using (var ms = new MemoryStream())              {                  new BinaryFormatter().Serialize(ms, value);                  return ms.ToArray();              }          }            protected virtual IEnumerable<RedisKey> GetKeys(EndPoint endPoint, string prefix = null)          {              var server = _connectionWrapper.GetServer(endPoint);                var keys = server.Keys(_db.Database, string.IsNullOrEmpty(prefix) ? null : $"{prefix}*");                keys = keys.Where(key => !key.ToString().Equals(CachingDefaults.RedisDataProtectionKey, StringComparison.OrdinalIgnoreCase));                return keys;          }            protected virtual IEnumerable<RedisKey> GetContainKeys(EndPoint endPoint, string contain = null)          {              var server = _connectionWrapper.GetServer(endPoint);                var keys = server.Keys(_db.Database, string.IsNullOrEmpty(contain) ? null : $"*{contain}*");                keys = keys.Where(key => !key.ToString().Equals(CachingDefaults.RedisDataProtectionKey, StringComparison.OrdinalIgnoreCase));                return keys;          }            protected virtual async Task<T> GetAsync<T>(string key)          {              if (_perRequestCacheManager.IsSet(key))                  return _perRequestCacheManager.Get(key, () => default(T), 0);              var serializedItem = await _db.StringGetAsync(key);              if (!serializedItem.HasValue)                  return default(T);                var item = JsonConvert.DeserializeObject<T>(serializedItem);              if (item == null)                  return default(T);              _perRequestCacheManager.Set(key, item, 0);              return item;          }              protected virtual async Task SetAsync(string key, object data, int cacheTime)          {              if (data == null)                  return;                var expiresIn = TimeSpan.FromMinutes(cacheTime);                var serializedItem = JsonConvert.SerializeObject(data);                await _db.StringSetAsync(key, serializedItem, expiresIn);          }              protected virtual async Task<bool> IsSetAsync(string key)          {              if (_perRequestCacheManager.IsSet(key))                  return true;              return await _db.KeyExistsAsync(key);          }            #endregion            #region Methods            /// <summary>          ///通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">,如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          public async Task<T> GetAsync<T>(string key, Func<Task<T>> acquire, int? cacheTime = null)          {              if (await IsSetAsync(key))                  return await GetAsync<T>(key);                var result = await acquire();                if ((cacheTime ?? CachingDefaults.CacheTime) > 0)                  await SetAsync(key, result, cacheTime ?? CachingDefaults.CacheTime);                return result;          }            /// <summary>          /// 通過Key獲取快取          /// </summary>          /// <typeparam name="T">>快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <returns>通過Key獲取到的特定的數據</returns>          public virtual T Get<T>(string key)          {              if (_perRequestCacheManager.IsSet(key))                  return _perRequestCacheManager.Get(key, () => default(T), 0);              var serializedItem = _db.StringGet(key);                if (!serializedItem.HasValue)                  return default(T);                var item = JsonConvert.DeserializeObject<T>(serializedItem);              if (item == null)                  return default(T);                _perRequestCacheManager.Set(key, item, 0);                return item;            }            /// <summary>          ///通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">,如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          public virtual T Get<T>(string key, Func<T> acquire, int? cacheTime = null)          {                if (IsSet(key))                  return Get<T>(key);                var result = acquire();                if ((cacheTime ?? CachingDefaults.CacheTime) > 0)                  Set(key, result, cacheTime ?? CachingDefaults.CacheTime);                return result;          }            /// <summary>          /// 設置快取          /// </summary>          /// <param name="key">Key</param>          /// <param name="data">Value</param>          /// <param name="cacheTime">快取時間(分鐘)</param>          public virtual void Set(string key, object data, int cacheTime)          {              if (data == null)                  return;                var expiresIn = TimeSpan.FromMinutes(cacheTime);                var serializedItem = JsonConvert.SerializeObject(data);                _db.StringSet(key, serializedItem, expiresIn);          }            /// <summary>          /// 判斷Key是否設置快取          /// </summary>          /// <param name="key">Keym</param>          /// <returns>True表示存在;false則不存在</returns>s>          public virtual bool IsSet(string key)          {              if (_perRequestCacheManager.IsSet(key))                  return true;              return _db.KeyExists(key);          }            /// <summary>          ///通過Key刪除快取數據          /// </summary>          /// <param name="key">Key</param>          public virtual void Remove(string key)          {              if (key.Equals(CachingDefaults.RedisDataProtectionKey, StringComparison.OrdinalIgnoreCase))                  return;                _db.KeyDelete(key);              _perRequestCacheManager.Remove(key);          }          /// <summary>          /// 刪除所有包含字元串的快取          /// </summary>          /// <param name="contain">包含的字元串</param>          public void RemoveByContain(string contain)          {              _perRequestCacheManager.RemoveByContain(contain);                foreach (var endPoint in _connectionWrapper.GetEndPoints())              {                  var keys = GetContainKeys(endPoint, contain);                    _db.KeyDelete(keys.ToArray());              }          }            /// <summary>          /// 刪除以prefix開頭的快取數據          /// </summary>          /// <param name="prefix">prefix開頭</param>          public virtual void RemoveByPrefix(string prefix)          {              _perRequestCacheManager.RemoveByPrefix(prefix);                foreach (var endPoint in _connectionWrapper.GetEndPoints())              {                  var keys = GetKeys(endPoint, prefix);                    _db.KeyDelete(keys.ToArray());              }          }            /// <summary>          /// 刪除所有的快取          /// </summary>          public virtual void Clear()          {              foreach (var endPoint in _connectionWrapper.GetEndPoints())              {                  var keys = GetKeys(endPoint).ToArray();                    foreach (var redisKey in keys)                  {                      _perRequestCacheManager.Remove(redisKey.ToString());                  }                    _db.KeyDelete(keys);              }          }            public virtual void Dispose()          {          }            #endregion      }  }

View Code

IStaticCacheManager Redis和MemoryCache統一暴露的介面

using System;  using System.Collections.Generic;  using System.Text;  using System.Threading.Tasks;    namespace Tools.Cache  {      /// <summary>      ///用於在HTTP請求之間進行快取的管理器(長期快取)      /// </summary>      public interface IStaticCacheManager : ICacheManager      {          /// <summary>          ///通過Key獲取快取,如果沒有該快取,則創建該快取,並返回數據          /// </summary>          /// <typeparam name="T">快取項Type</typeparam>          /// <param name="key">快取 key</param>          /// <param name="acquire">,如果該Key沒有快取則通過方法載入數據</param>          /// <param name="cacheTime">快取分鐘數; 0表示不快取; null則使用默認快取時間</param>          /// <returns>通過Key獲取到的特定的數據</returns>          Task<T> GetAsync<T>(string key, Func<Task<T>> acquire, int? cacheTime = null);      }  }

View Code

配置是否使用Redis作為快取,默認使用MemoryCache

  "Cache": {      "RedisEnabled": true,      "RedisDatabaseId": "",      "RedisConnectionString": "127.0.0.1:6379,ssl=False",      "UseRedisToStoreDataProtectionKeys": true,      "UseRedisForCaching": true,      "UseRedisToStorePluginsInfo": true    }

View Code

讀取配置擴展ServiceCollectionExtensions

using Microsoft.Extensions.Configuration;  using Microsoft.Extensions.DependencyInjection;  using System;  using System.Collections.Generic;  using System.Text;    namespace Infrastructure.Common.Extensions  {      public static class ServiceCollectionExtensions      {          public static TConfig ConfigureStartupConfig<TConfig>(this IServiceCollection services, IConfiguration configuration) where TConfig : class, new()          {              if (services == null)                  throw new ArgumentNullException(nameof(services));                if (configuration == null)                  throw new ArgumentNullException(nameof(configuration));                var config = new TConfig();                configuration.Bind(config);                services.AddSingleton(config);                return config;          }      }  }

View Code

依賴注入:我們這裡採用autofac來實現依賴注入,在Startup.cs中的ConfigureServices方法中添加:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();  var build =                 new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())//SetBasePath設置配置文件所在路徑                 .AddJsonFile("appsettings.json");              var configRoot = build.Build();              var config = services.ConfigureStartupConfig<StartupConfig>(configRoot.GetSection("Cache"));              var builder = new ContainerBuilder();  #region 自動判斷是否使用Redis,TURE則使用Redis,否則使用本機記憶體快取              if (config.RedisEnabled)              {                  //services.                  builder.RegisterType<RedisConnectionWrapper>()                      .As<ILocker>()                      .As<IRedisConnectionWrapper>()                      .SingleInstance();              }              //static cache manager              if (config.RedisEnabled && config.UseRedisForCaching)              {                  builder.RegisterType<RedisCacheManager>().As<IStaticCacheManager>()                      .InstancePerLifetimeScope();              }              else              {                  builder.RegisterType<MemoryCacheManager>()                      .As<ILocker>()                      .As<IStaticCacheManager>()                      .SingleInstance();              }                #endregion  services.AddEasyCaching(option =>              {                  //use memory cache                  option.UseInMemory("default");              });  var container = builder.Build();              return new AutofacServiceProvider(container);//autofac 接管.netCore默認DI

View Code

在Configure方法中添加:

 app.UseEasyCaching(); 

至此,MemoryCache與Redis的使用到此結束。
最後說一下添加RemoveByContain介面的目的:看方法就是刪除包含字元串的快取對象,目的:在底層操作數據時,防止數據更新了,快取還存在,不能訪問最新的數據,如何做到實時同步,看上篇部落格底層的方法,在對單表操作時快取Key都包含了實體的名稱,因為後期我們可能還會涉及到一些多表的查詢,此時,只需要在設置多表查詢時快取Key都包含每個實體的名稱,後面再更新或刪除數據時通過包含字元串就可以移除掉所有相關的快取,保證了數據及時的一致性。