• 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开发框架目录

  • 相关阅读:
    第27篇-虚拟机字节码指令之操作数栈管理指令
    第26篇-虚拟机对象操作指令之putstatic
    第25篇-虚拟机对象操作指令之getfield
    第24篇-虚拟机对象操作指令之getstatic
    第23篇-虚拟机字节码指令之类型转换
    第22篇-虚拟机字节码之运算指令
    第20篇-加载与存储指令之ldc与_fast_aldc指令(2)
    第19篇-加载与存储指令(1)
    第18章-x86指令集之常用指令
    第17章-x86-64寄存器
  • 原文地址:https://www.cnblogs.com/BenDan2002/p/5999038.html
Copyright © 2020-2023  润新知