4.3 可配置的分布式缓存(上)

为了加快系统运行效率,一般情况下系统会采用缓存技术,将常用信息存放到缓存中,避免频繁的从数据库、文件中读写,造成系统瓶颈,从而提高响应速度。缓存分为客户端缓存和服务器端缓存。

目前随着系统的扩展,服务器端缓存一般采取两级缓存技术,本地缓存和分布式缓存。部分常用、公共或者小数据量的信息保存在分布式缓存中,运行在不同资源上的系统均从分布式缓存中获取同样的数据。相反,常用、私有或者数据量大的信息则保存在本地缓存中,避免了大数据量信息频繁网络传输、序列化和反序列化造成的系统瓶颈。

 

大家在使用分布式缓存中需要注意的是,应用在将数据存入分布式缓存或者读取时,数据需要序列化才能存入,读取时反序列化,这样对于大的对象占用的网络/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开发框架目录

posted @ 2016-10-29 19:07  BenDan2002  阅读(2778)  评论(0编辑  收藏  举报