為了加快系統運作效率,一般情況下系統會采用緩存技術,将常用資訊存放到緩存中,避免頻繁的從資料庫、檔案中讀寫,造成系統瓶頸,進而提高響應速度。緩存分為用戶端緩存和伺服器端緩存。
目前随着系統的擴充,伺服器端緩存一般采取兩級緩存技術,本地緩存和分布式緩存。部分常用、公共或者小資料量的資訊儲存在分布式緩存中,運作在不同資源上的系統均從分布式緩存中擷取同樣的資料。相反,常用、私有或者資料量大的資訊則儲存在本地緩存中,避免了大資料量資訊頻繁網絡傳輸、序列化和反序列化造成的系統瓶頸。
大家在使用分布式緩存中需要注意的是,應用在将資料存入分布式緩存或者讀取時,資料需要序列化才能存入,讀取時反序列化,這樣對于大的對象占用的網絡/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) {…}。
面向雲的.net core開發架構目錄