一、.MemoryCache介紹 MemoryCache是.Net Framework 4.0開始提供的記憶體緩存類,使用該類型可以方便的在程式內部緩存數據並對於數據的有效性進行方便的管理, 它通過在記憶體中緩存數據和對象來減少讀取資料庫的次數,從而減輕資料庫負載,加快數據讀取速度,提升系統的性能。 二 ...
一、.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添加介面
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <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);
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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中添加代碼
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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中實現介面
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public int RemoveByContain(string contain) { var keysToRemove = _memory.Keys.Where(x => x.Contains(contain)).ToList(); return RemoveAll(keysToRemove); }View Code
MemoryCache介面實現:MemoryCacheManager
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>