為了加快系統運行效率,一般情況下系統會採用緩存技術,將常用信息存放到緩存中,避免頻繁的從資料庫、文件中讀寫,造成系統瓶頸,從而提高響應速度。緩存分為客戶端緩存和伺服器端緩存。 目前隨著系統的擴展,伺服器端緩存一般採取兩級緩存技術,本地緩存和分散式緩存。部分常用、公共或者小數據量的信息保存在分散式緩存 ...
為了加快系統運行效率,一般情況下系統會採用緩存技術,將常用信息存放到緩存中,避免頻繁的從資料庫、文件中讀寫,造成系統瓶頸,從而提高響應速度。緩存分為客戶端緩存和伺服器端緩存。
目前隨著系統的擴展,伺服器端緩存一般採取兩級緩存技術,本地緩存和分散式緩存。部分常用、公共或者小數據量的信息保存在分散式緩存中,運行在不同資源上的系統均從分散式緩存中獲取同樣的數據。相反,常用、私有或者數據量大的信息則保存在本地緩存中,避免了大數據量信息頻繁網路傳輸、序列化和反序列化造成的系統瓶頸。
大家在使用分散式緩存中需要註意的是,應用在將數據存入分散式緩存或者讀取時,數據需要序列化才能存入,讀取時反序列化,這樣對於大的對象占用的網路/CPU等資源會比較多。
通常情況下,.net core的緩存使用是這樣的:
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddMemoryCache(); 4 // Add framework services. 5 services.AddMvc(); 6 } 7 8 public class HomeController : Controller 9 { 10 private IMemoryCache _memoryCache; 11 public HomeController(IMemoryCache memoryCache) 12 { 13 _memoryCache = memoryCache; 14 } 15 16 public IActionResult Index() 17 { 18 string cacheKey = "key"; 19 string result; 20 if (!_memoryCache.TryGetValue(cacheKey, out result)) 21 { 22 result = DateTime.Now.ToString(); 23 _memoryCache.Set(cacheKey, result); 24 } 25 ViewBag.Cache = result; 26 return View(); 27 } 28 }
第一段是在startup.cs中註冊緩存,第二段是具體在某個controller中的使用。
對於分散式緩存來說,以Redis為例則需要寫成這樣:
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddDistributedRedisCache(options => 4 { 5 options.Configuration = "localhost"; 6 options.InstanceName = "SampleInstance"; 7 }); 8 } 9 10 public class HomeController : Controller 11 { 12 private IDistributedCache _memoryCache; 13 public HomeController(IDistributedCache memoryCache) 14 { 15 _memoryCache = memoryCache; 16 } 17 18 public IActionResult Index() 19 { 20 string cacheKey = "key"; 21 string result; 22 if (!_memoryCache.TryGetValue(cacheKey, out result)) 23 { 24 result = DateTime.Now.ToString(); 25 _memoryCache.Set(cacheKey, result); 26 } 27 ViewBag.Cache = result; 28 return View(); 29 } 30 }
然而,這種分散式緩存編程方式在大型可擴展的系統中存在一些問題。一是在不同的環境中可能緩存產品不同,例如阿裡雲是memcached,微軟是appfabirc,我們本地集群的是redis等。不能每次部署修改一遍程式吧?這違反了可替換、可配置的軟體質量原則了。二是IDistributedCache介面方法用起來比較繁瑣,對於過期時間等還得自行編寫DistributedCacheEntryOptions。其實過期時間一般都是滑動時間,應該可以將這個時間直接寫在配置文件中,而不要每個緩存自己寫。
為此,對分散式緩存的封裝首先是建立更好的使用介面,其次是可配置化。介面和抽象類的代碼如下:
1 public interface ICacheHander 2 { 3 /// <summary> 4 /// 如果不存在緩存項則添加,否則更新 5 /// </summary> 6 /// <param name="catalog">緩存分類</param> 7 /// <param name="key">緩存項標識</param> 8 /// <param name="value">緩存項</param> 9 void Put<T>(string catalog, string key, T value); 10 11 /// <summary> 12 /// 如果不存在緩存項則添加,否則更新 13 /// </summary> 14 /// <param name="catalog">緩存分類</param> 15 /// <param name="key">緩存項標識</param> 16 /// <param name="value">緩存項</param> 17 /// <param name="timeSpan">緩存時間,滑動過期</param> 18 void Put<T>(string catalog, string key, T value, TimeSpan timeSpan); 19 20 /// <summary> 21 /// 獲取緩存項 22 /// </summary> 23 /// <param name="catalog">緩存分類</param> 24 /// <param name="key">cacheKey</param> 25 T Get<T>(string catalog, string key); 26 27 /// <summary> 28 /// 獲取緩存項,如果不存在則使用方法獲取數據並加入緩存 29 /// </summary> 30 /// <typeparam name="T"></typeparam> 31 /// <param name="catalog">緩存分類</param> 32 /// <param name="key">緩存項標識</param> 33 /// <param name="func">獲取要緩存的數據的方法</param> 34 /// <returns>緩存的數據結果</returns> 35 T GetOrAdd<T>(string catalog, string key, Func<T> func); 36 37 /// <summary> 38 /// 獲取緩存項,如果不存在則使用方法獲取數據並加入緩存 39 /// </summary> 40 /// <typeparam name="T"></typeparam> 41 /// <param name="catalog">緩存分類</param> 42 /// <param name="key">緩存項標識</param> 43 /// <param name="func">獲取要緩存的數據的方法</param> 44 /// <param name="timeSpan">緩存時間,滑動過期</param> 45 /// <returns>緩存的數據結果</returns> 46 T GetOrAdd<T>(string catalog, string key, Func<T> func, TimeSpan timeSpan); 47 48 /// <summary> 49 /// 移除緩存項 50 /// </summary> 51 /// <param name="catalog">緩存分類</param> 52 /// <param name="key">cacheKey</param> 53 void Remove(string catalog, string key); 54 55 /// <summary> 56 /// 刷新緩存項 57 /// </summary> 58 /// <param name="catalog">緩存分類</param> 59 /// <param name="key">cacheKey</param> 60 void Refresh(string catalog, string key); 61 } 62 63 64 public abstract class BaseCacheHandler : ICacheHander 65 { 66 protected abstract IDistributedCache _Cache { get; } 67 protected CachingConfigInfo _ConfigInfo { get; private set; } 68 69 private TimeSpan _DefaultTimeSpan; 70 71 private ConcurrentDictionary<string, object> _LockObjsDic; 72 73 public BaseCacheHandler(CachingConfigInfo configInfo) 74 { 75 this._DefaultTimeSpan = new TimeSpan(0, configInfo.DefaultSlidingTime, 0); 76 this._LockObjsDic = new ConcurrentDictionary<string, object>(); 77 78 this._ConfigInfo = configInfo; 79 } 80 81 public void Put<T>(string catalog, string key, T value) => Put(catalog, key, value, _DefaultTimeSpan); 82 83 public virtual void Put<T>(string catalog, string key, T value, TimeSpan timeSpan) 84 { 85 string cacheKey = GenCacheKey(catalog, key); 86 87 string str = SerializerHelper.ToJson<T>(value); 88 89 _Cache.SetString(cacheKey, str, new DistributedCacheEntryOptions().SetSlidingExpiration(timeSpan)); 90 } 91 92 public T Get<T>(string catalog, string key) 93 { 94 MicroStrutLibraryExceptionHelper.TrueThrow(string.IsNullOrWhiteSpace(catalog) || string.IsNullOrWhiteSpace(key), this.GetType().FullName, LogLevel.Error, "緩存分類或者標識不能為空"); 95 96 string cacheKey = GenCacheKey(catalog, key); 97 98 string str = _Cache.GetString(cacheKey); 99 100 return SerializerHelper.FromJson<T>(str); 101 } 102 103 public T GetOrAdd<T>(string catalog, string key, Func<T> func) => GetOrAdd(catalog, key, func, _DefaultTimeSpan); 104 105 public T GetOrAdd<T>(string catalog, string key, Func<T> func, TimeSpan timeSpan) 106 { 107 MicroStrutLibraryExceptionHelper.TrueThrow(string.IsNullOrWhiteSpace(catalog) || string.IsNullOrWhiteSpace(key), this.GetType().FullName, LogLevel.Error, "緩存分類或者標識不能為空"); 108 109 T result = Get<T>(catalog, key); 110 111 if (result == null) 112 { 113 string cacheKey = GenCacheKey(catalog, key); 114 115 object lockObj = _LockObjsDic.GetOrAdd(cacheKey, n => new object()); 116 lock (lockObj) 117 { 118 result = Get<T>(catalog, key); 119 120 if (result == null) 121 { 122 result = func(); 123 Put(catalog, key, result, timeSpan); 124 } 125 } 126 } 127 128 if (result == null) 129 return default(T); 130 131 return result; 132 } 133 134 /// <summary> 135 /// 刪除緩存 136 /// </summary> 137 /// <param name="catalog"></param> 138 /// <param name="key"></param> 139 public void Remove(string catalog, string key) 140 { 141 string cacheKey = GenCacheKey(catalog, key); 142 143 _Cache.Remove(cacheKey); 144 } 145 146 /// <summary> 147 /// 清空緩存 148 /// </summary> 149 public void Refresh(string catalog, string key) 150 { 151 string cacheKey = GenCacheKey(catalog, key); 152 153 _Cache.Refresh(cacheKey); 154 } 155 156 /// <summary> 157 /// 生成緩存鍵 158 /// </summary> 159 /// <param name="catalog"></param> 160 /// <param name="key"></param> 161 /// <returns></returns> 162 private string GenCacheKey(string catalog, string key) 163 { 164 return $"{catalog}-{key}"; 165 } 166 }
抽象類又對介面進行了進一步的實現。大家可以註意下緩存Get時我們使用了兩次lock,還有GetOrAdd方法,我們用了一個func。
緩存配置信息的代碼,配置基類和配置的介紹請見:多級可換源的配置實現。
1 public sealed class CachingConfigInfo : ConfigInfo 2 { 3 /// <summary> 4 /// 緩存滑動視窗時間(分鐘) 5 /// </summary> 6 public int DefaultSlidingTime { get; set; } 7 8 /// <summary> 9 /// 分散式緩存類型TypeDescription,例如是Redis/Memcached/Default等分散式緩存對應的實現類信息。 10 /// </summary> 11 public string Type { get; set; } 12 13 /// <summary> 14 /// 緩存的參數,一般是伺服器信息 15 /// </summary> 16 public List<CacheServer> Servers { get; set; } 17 18 public override string SectionName 19 { 20 get 21 { 22 return "MicroStrutLibrary:Caching"; 23 } 24 } 25 26 public override void RegisterOptions(IServiceCollection services, IConfigurationRoot root) 27 { 28 services.Configure<CachingConfigInfo>(root.GetSection(SectionName)); 29 } 30 } 31 32 /// <summary> 33 /// 分散式緩存 伺服器配置項 34 /// </summary> 35 public sealed class CacheServer 36 { 37 /// <summary> 38 /// 伺服器地址,可以使伺服器網路名稱也可是IP地址 39 /// </summary> 40 public string HostName { get; set; } 41 42 /// <summary> 43 /// 伺服器分散式緩存服務提供埠 44 /// </summary> 45 public int Port { get; set; } 46 }
其中Type屬性就是具體的實現,例如Redis分散式緩存的實現等。具體請見下節。
接下來需要在startup註冊使用分散式緩存處理
1 public static IServiceCollection AddCacheHandler(this IServiceCollection services) 2 { 3 if (services == null) 4 { 5 throw new ArgumentNullException(nameof(services)); 6 } 7 8 IOptions<CachingConfigInfo> optionsAccessor = services.BuildServiceProvider().GetService<IOptions<CachingConfigInfo>>(); 9 10 ICacheHander handler = ReflectionHelper.CreateInstance(optionsAccessor.Value.Type, optionsAccessor.Value) as ICacheHander; 11 12 services.TryAddSingleton<ICacheHander>(handler); 13 14 return services; 15 }
在使用分散式緩存時,只需要在方法或者調用類的構造函數上增加ICacheHandler cacheHandler的屬性即可。例如ParameterService 類的構造函數public ParameterService(ICacheHandler cacheHandler) {…}。