序言

​ 今天这篇文章来看看Masa Framework的缓存设计,上一篇文章中说到的MasaFactory的应用也会在这章节出现。文章中如有错误之处还请指点,咱们话不多说,直入主题。

Masa Framework缓存简介

MASA Framework源码地址:https://github.com/masastack/MASA.Framework

​ Masa Framework中的缓存组件支持 分布式缓存分布式多级缓存 (PS:Masa Framework的缓存组件并不与框架强行绑定,也就是说我们可以在自己的框架中使用masa framework的缓存组件,而不是我必须用masa framework才能使用它的缓存组件,这一点必须给官方点个大大的赞)。首先分布式缓存大家多多少少都听说过也用过,在这不多介绍分布式缓存的概念。我们来看下多级缓存吧,其实多级缓存这个概念很早就有,但是在.net中没怎么看到这个设计的落地实现框架,今天刚好借着解读masa framework的源码,我们来看下多级缓存设计。

多级缓存的定义

什么是多级缓存?既然已经有了分布式缓存,为什么还要多级缓存?

​ 首先什么是多级缓存?多级缓存是指在一个系统的不同架构层级进行数据缓存,以提升访问效率。其次有了分布式缓存,为什么还要多级缓存?是因为在读取数据频率很高的情况下,分布式缓存面临着两个问题:响应速度和高可用。响应速度问题是指当访问层发一起一个网络请求到分布式缓存处理完请求返回的时间,是需要一个过程,而网络请求的不确定性以及耗时时长是不可避免的。而高可用问题是指大量读取数据请求过来读取缓存的时候,分布式缓存能否扛得住这么大的压力,当然这个有解决方案可以使用集群解决,但是集群之后会有数据一致性问题并且它读取数据还是得走网络通信。

​ 而多级缓存就是为了优化分布式缓存存在的一些问题,而衍生另一种手段。所谓多级缓存可以简单理解为是通过在分布式缓存和我们的访问层中间在增加若干层缓存来减少对分布式缓存的网络请求和分布式缓存的压力。(PS:多级缓存存在的数据一致性问题,这个Masa Framework已经帮我们解决了

MASA Framework中的多级缓存设计

首先Masa Framework有一套分布式缓存接口及实现,而Masa Framework的多级缓存是在分布式缓存的基础上,在加了一层内存缓存。而多级缓存数据的一致性问题,masa framework是通过redis的pub、sub发布订阅解决的(PS:这块的发布订阅官方有一个抽象,并不直接依赖redis,请往下看)。

  • 当访问层读取缓存数据时,先从内存里面获取下,如果没有则向分布式缓存获取并写入到内存缓存,并且同时开启一个关于缓存key的分布式订阅,如果收到消息则同步更新内存缓存。

  • 当访问层写入缓存时,同时写入内存以及分布式缓存,然后再发布关于缓存key的分布式消息,其它客户端收到消息时则同步更新各自内存缓存数据。

源码解读

接下来让我们来看下Masa Framework的源码设计,首先我们把源码下载下来,然后打开。下载地址:https://github.com/masastack/MASA.Framework

源码目录结构

  • masa framework缓存组件分为两部分,一个是BuildingBlocks下的Caching抽象接口,另外一个是Contrib下的Caching接口实现。结构如下图:

代码设计

Masa Framework整个缓存组件分为三个类库项目,分别是:Masa.BuildingBlocks.CachingMasa.Contrib.Caching.Distributed.StackExchangeRedisMasa.Contrib.Caching.MultilevelCache

​ 首先Masa.BuildingBlocks.Caching这个类库就是将我们经常用到的缓存方法抽象了一层(IDistributedCacheClient、IMultilevelCacheClient),其中包含分布式缓存以及多级缓存常用的方法,如:Get、Set、Refresh、Remove,分布式缓存中的(Subscribe、Publish等)。

​ 而Masa.Contrib.Caching.Distributed.StackExchangeRedis 这个类库实现了分布式缓存(PS:这个库没有实现多级缓存IMultilevelCacheClient接口,个人觉得其实应该将Masa.BuildingBlocks.Caching这个类库再拆分出两个包,将分布式和多级缓存分开)。

​ 最后Masa.Contrib.Caching.MultilevelCache这个类库实现了多级缓存(这个类库没有实现分布式缓存IDistributedCacheClient接口,但是多级缓存依赖了IDistributedCacheClient)。最终整个缓存的设计如下图所示:

  • Masa.BuildingBlocks.Caching :这个类库包含了分布式缓存和多级缓存的抽象接口以及抽象基类

    • ICacheClient:缓存公共方法抽象(把多级缓存和分布式缓存都有的方法在封装一层,如:Get、Set、Refersh等方法)
    • CacheClientBase :缓存抽象基类,对方法进行封装(比如Get、GetList,最终都调用GetList方法等)
    • IDistributedCacheClient :分布式缓存接口抽象(Get、Set、Refersh、Publish、Subscribe等方法),继承ICacheClient
    • DistributedCacheClientBase :分布式缓存抽象基类,对方法进行封装(比如Get、GetList,最终都调用GetList方法等)
    • IMultilevelCacheClient :多级缓存接口抽象(Get、Set、Refersh等方法),继承ICacheClient
    • MultilevelCacheClientBase :多级缓存抽象基类,对方法进行封装(比如Get、GetList,最终都调用GetList方法等)
    • 构建工厂
      • ICacheClientFactory<TService> :缓存工厂抽象,继承自IMasaFactory<TService> 构建工厂
      • CacheClientFactoryBase<TService> :缓存工厂抽象基类,继承自MasaFactoryBase<TService>
      • IDistributedCacheClientFactory :用于创建分布式缓存IDistributedCacheClient 接口,继承自ICacheClientFactory<IDistributedCacheClient>
      • DistributedCacheClientFactoryBase :分布式缓存创建工厂实现类,创建IDistributedCacheClient 接口实例。
      • IMultilevelCacheClientFactory :用于创建多级缓存IMultilevelCacheClient 接口,继承自ICacheClientFactory<IMultilevelCacheClient>
      • MultilevelCacheClientFactoryBase :多级缓存创建工厂实现类,创建IMultilevelCacheClient 接口实例。
  • Masa.Contrib.Caching.Distributed.StackExchangeRedis : 分布式缓存IDistributedCacheClient接口的实现

    • RedisCacheClientBase :redis实现分布式缓存接口,进行再一步封装,将redis连接、订阅、配置等初始化。继承DistributedCacheClientBase
    • RedisCacheClient :分布式缓存的redis实现,继承RedisCacheClientBase
  • Masa.Contrib.Caching.MultilevelCache :多级缓存实现

    • MultilevelCacheClient :多级缓存实现,内部依赖IDistributedCacheClient

整个缓存组件的设计,最主要类是这些,当然还有一些option配置和帮助类,我就没有画出来,这个留待大家自己去探索

Demo案例

Demo案例项目地址:https://github.com/MapleWithoutWords/masa-demos/tree/main/src/CachingDemo

上面也说到Masa Framework的缓存组件不与框架强绑定,也就是说我们可以在自己的框架中使用masa的缓存组件,下面我将展示两个项目,它们分别使用分布式缓存和多级缓存。

分布式缓存使用

  1. 第一步,在我们的项目中安装分布式缓存组件Masa.Contrib.Caching.Distributed.StackExchangeRedis (下载1.0.0-preview.18版本以上),或在项目目录下使用命令行安装
dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 1.0.0-preview.18
  1. 第二步,在Program.cs文件中添加以下代码
builder.Services.AddDistributedCache(opt =>
{
opt.UseStackExchangeRedisCache();
});
  1. 第三步,在配置文件中增加以下配置。这边再补充以下,masa的redis分布式缓存是支持集群的,只需要在Servers下配置多个节点就行
"RedisOptions": {
"Servers": [
{
"Host": "127.0.0.1",
"Port": "6391"
}
],
"DefaultDatabase": 0,
"Password": "123456"
}
  1. 第四步:在构造函数中注入 IDistributedCacheClient 或者 IDistributedCacheClientFactory 对象,其实直接注入的IDistributedCacheClient 也是由IDistributedCacheClientFactory 创建之后,注入到容器中的单例对象。

    • 构造函数中注入 IDistributedCacheClient :这个注入的对象生命周期为单例,也就是说从容器中获取的始终是同一个对象
        public class DistributedCacheClientController : ControllerBase
    {
    private static readonly string[] Summaries = new[] { "Data1", "Data2", "Data3" };
    private readonly IDistributedCacheClient _distributedCacheClient;
    public DistributedCacheClientController(IDistributedCacheClient distributedCacheClient) => _distributedCacheClient = distributedCacheClient; [HttpGet]
    public async Task<IEnumerable<string>> Get()
    {
    var cacheList = await _distributedCacheClient.GetAsync<string[]>(nameof(Summaries));
    if (cacheList != null)
    {
    Console.WriteLine($"从缓存中获取数据:【{string.Join(",", cacheList)}】");
    return cacheList;
    }
    Console.WriteLine($"写入数据到缓存");
    await _distributedCacheClient.SetAsync(nameof(Summaries), Summaries);
    return Summaries;
    }
    }
    • 使用IDistributedCacheClientFactory :使用工厂创建的每一个对象都是一个新的实例,需要手动管理对象生命周期,比如不使用之后要dispose。扩展:这块还可以使用自己实现的IDistributedCacheClient实例去操作,不太理解的可以看下我上篇文章 。不过建议直接注入IDistributedCacheClient 使用,不太推荐工厂,除非你有场景需要用到一个新的实例。
    public class DistributedCacheClientFactoryController : ControllerBase
    {
    private static readonly string[] FactorySummaries = new[] { "FactoryData1", "FactoryData2", "FactoryData3" };
    private readonly IDistributedCacheClientFactory _distributedCacheClientFactory;
    public DistributedCacheClientFactoryController(IDistributedCacheClientFactory distributedCacheClientFactory) => _distributedCacheClientFactory = distributedCacheClientFactory; [HttpGet]
    public async Task<IEnumerable<string>> GetByFactory()
    {
    using (var distributedCacheClient = _distributedCacheClientFactory.Create())
    {
    var cacheList = await distributedCacheClient.GetAsync<string[]>(nameof(FactorySummaries));
    if (cacheList != null)
    {
    Console.WriteLine($"使用工厂从缓存中获取数据:【{string.Join(",", cacheList)}】");
    return cacheList;
    }
    Console.WriteLine($"使用工厂写入数据到缓存");
    await distributedCacheClient.SetAsync(nameof(FactorySummaries), FactorySummaries);
    return FactorySummaries;
    }
    }
    }
  2. 最终结果:注:记得启动本地redis

多级缓存使用

  1. 第一步,在我们的项目中安装组件: Masa.Contrib.Caching.MultilevelCacheMasa.Contrib.Caching.Distributed.StackExchangeRedis (下载1.0.0-preview.18版本以上),或在项目目录下使用命令行安装
dotnet add package Masa.Contrib.Caching.MultilevelCache --version 1.0.0-preview.18
dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 1.0.0-preview.18
  1. 第二步,在Program.cs文件中添加以下代码
builder.Services.AddMultilevelCache(opt =>
{
opt.UseStackExchangeRedisCache();
});
  1. 第三步,在配置文件中增加以下配置。多级缓存依赖于分布式缓存,所以需要添加redis配置,如下:
"RedisOptions": {
"Servers": [
{
"Host": "127.0.0.1",
"Port": "6391"
}
],
"DefaultDatabase": 0,
"Password": "123456"
}
  1. 第四步:在构造函数中注入 IMultilevelCacheClient 或者 IMultilevelCacheClientFactory 对象。

    • 构造函数中注入 IMultilevelCacheClient :这个注入的对象生命周期为单例,也就是说从容器中获取的始终是同一个对象
    public class MultilevelCacheClientController : ControllerBase
    {
    private readonly IMultilevelCacheClient _multilevelCacheClient;
    public MultilevelCacheClientController(IMultilevelCacheClient multilevelCacheClient) => _multilevelCacheClient = multilevelCacheClient; [HttpGet]
    public async Task<string> GetAsync()
    {
    var key = "MultilevelCacheFactoryTest";
    var cacheValue = await _multilevelCacheClient.GetAsync<string>(key);
    if (cacheValue != null)
    {
    Console.WriteLine($"get data by multilevel cahce:【{cacheValue}】");
    return cacheValue;
    }
    cacheValue = value;
    Console.WriteLine($"write data【{cacheValue}】to multilevel cache");
    await _multilevelCacheClient.SetAsync(key, cacheValue);
    return cacheValue;
    }
    }
    • 使用IDistributedCacheClientFactory :使用工厂创建的每一个对象都是一个新的实例,需要手动管理对象生命周期,比如不使用之后要dispose。建议直接注入IDistributedCacheClient 使用,不太推荐工厂,除非你有场景需要用到一个新的实例。
    public class MultilevelCacheClientController : ControllerBase
    {
    const string key = "MultilevelCacheTest";
    private readonly IMultilevelCacheClient _multilevelCacheClient;
    public MultilevelCacheClientController(IMultilevelCacheClient multilevelCacheClient) => _multilevelCacheClient = multilevelCacheClient; [HttpGet]
    public async Task<string?> GetAsync()
    {
    var cacheValue = await _multilevelCacheClient.GetAsync<string>(key, val => { Console.WriteLine($"值被改变了:{val}"); }, null);
    if (cacheValue != null)
    {
    Console.WriteLine($"get data by multilevel cahce:【{cacheValue}】");
    return cacheValue;
    }
    cacheValue = "multilevelClient";
    Console.WriteLine($"use factory write data【{cacheValue}】to multilevel cache");
    await _multilevelCacheClient.SetAsync(key, cacheValue);
    return cacheValue;
    } [HttpPost]
    public async Task<string?> SetAsync(string value = "multilevelClient")
    {
    Console.WriteLine($"use factory write data【{value}】to multilevel cache");
    await _multilevelCacheClient.SetAsync(key, value);
    return value;
    } [HttpDelete]
    public async Task RemoveAsync()
    {
    await _multilevelCacheClient.RemoveAsync<string>(key);
    }
    }
  2. 运行程序

    • 我这边启动以命令行启动了两个服务模拟不同服务或者集群

      dotnet run --urls=http://*:2001
      dotnet run --urls=http://*:2002
    • 在端口2001的程序写入数据之后,端口2002的程序能够读取到数据

    • 在端口2001的程序修改 缓存数据 ,端口2002的程序能够同步新的缓存数据过来

总结

其实任何语言都能实现这个多级缓存功能,我们去看框架源码,不仅是对功能原理的探索,也是学习别人的设计思想。

  • 提升访问速度,降低分布式缓存压力:masa的多级缓存优先从内存读取数据,提高程序访问速度。间接减少网络请求,降低了分布式缓存的压力。

  • 缓存高度扩展性:MASA Framework的缓存组件可以支持自己去实现自己的缓存逻辑,比如说目前masa的分布式缓存使用redis,我想用其它的缓存组件,或者我觉得masa实现的不优雅,完全可以自己定制。

Masa Framework源码解读-02缓存模块(分布式缓存进阶之多级缓存)的更多相关文章

  1. 虎说:bootstrap源码解读(重置模块)

    ------<!--action-->------ 开场show:前不生“不犹豫”,后半生“不后悔”.今天又逃课,我不后悔 素材:推特公司的前端框架bootstrap(下称bt),解读源码 ...

  2. Vue源码学习02 初始化模块init.js

    接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是 ...

  3. jvm源码解读--02 Array<u1>* tags = MetadataFactory::new_writeable_array<u1>(loader_data, length, 0, CHECK_NULL); 函数引入的jvm内存分配解析

    current路径: #0 Array<unsigned char>::operator new (size=8, loader_data=0x7fd4c802e868, length=8 ...

  4. Webpack探索【16】--- 懒加载构建原理详解(模块如何被组建&如何加载)&源码解读

    本文主要说明Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack懒加载构建原理. 本文使用的 ...

  5. Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读

    本文主要说明Webpack模块构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack的基础构建原理. 本文使用的W ...

  6. Abp 审计模块源码解读

    Abp 审计模块源码解读 Abp 框架为我们自带了审计日志功能,审计日志可以方便地查看每次请求接口所耗的时间,能够帮助我们快速定位到某些性能有问题的接口.除此之外,审计日志信息还包含有每次调用接口时客 ...

  7. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  8. seajs 源码解读

    之前面试时老问一个问题seajs 是怎么加载js 文件的 在网上找一些资料,觉得这个写的不错就转载了,记录一下,也学习一下 seajs 源码解读 seajs 简单介绍 seajs是前端应用模块化开发的 ...

  9. SDWebImage源码解读之SDWebImagePrefetcher

    > 第十篇 ## 前言 我们先看看`SDWebImage`主文件的组成模块: ![](http://images2015.cnblogs.com/blog/637318/201701/63731 ...

  10. SDWebImage源码解读之干货大总结

    这是我认为的一些重要的知识点进行的总结. 1.图片编码简介 大家都知道,数据在网络中是以二进制流的形式传播的,那么我们该如何把那些1和0解析成我们需要的数据格式呢? 说的简单一点就是,当文件都使用二进 ...

随机推荐

  1. 049_Search Lookup (二)

    其实就是 在父object中 设置,search setting 中选中 enhanced lookup, and select the dialoge & Filter  默认looukp搜 ...

  2. vue的易错点合集

    关于vue的操作,可以借鉴到一些Ajax的方法和思路,但是因为语法的不一样,所以易错点多在语法. 第一步要引用相对的方法 div的id名称应该与下文的el名称一致 挂载方法created,相当于aja ...

  3. 微信小程序注册、登录小功能都在这

    微信小程序实现注册.登录页面的小功能整理,希望对大家有帮助. 1. 正则验证手机号码 var mobile = that.data.phone;     var myreg = /^(((13[0-9 ...

  4. WEB攻击与防御技术 pikachu——关于暴力破解

    首先打开XAMPP 然后在网上下载pikachu平台压缩包 解压缩即可.之后进入到XAMPP的文件夹 将pikachu文件夹放到htdoces内就完成了pikachu平台的搭建~ 之后在xampp中点 ...

  5. 3.17阿里Java后端,电商 sku 的全排列算法

    3.17阿里Java后端 字典 有英文字典:Map<Character, String[]>,示例如下: a : [a, an] b : [bus, bird] c : [car, can ...

  6. hive:使用concat_ws实现 array转string案例

    concat_ws(',',collect_set(if(step_name <> '',step_name,null))) AS step_names,

  7. es6中箭头函数和this指向

    箭头函数相当于匿名函数,简化了函数定义. 箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return. 另一种是包含多条语句,不可以省略{}和return. 特点 箭头函数最大的特点就是没 ...

  8. PTA1003 我要通过! (20 分)

    PTA1003 我要通过! (20 分) "答案正确"是自动判题系统给出的最令人欢喜的回复.本题属于 PAT 的"答案正确"大派送 -- 只要读入的字符串满足下 ...

  9. AXI4_LITE总线vivado2019.1官方模板源码(verilog实现)

    AXI lite总线读写时序 1. AXI_SLAVE源码 `timescale 1 ns / 1 ps module myip_v1_0_S00_AXI # ( // Users to add pa ...

  10. xml与DataSet互转

    //将DataSet转换为xml字符串   public static string ConvertDataSetToXMLFile(DataSet xmlDS, Encoding encoding) ...