序言

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

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

多级缓存使用

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

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

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

      1. dotnet run --urls=http://*:2001
      2. 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. Python_DL_July_深度学习_10_深度学习与各种迁移学习应用

    深度学习10(上)深度学习与各种迁移学习应用

  2. 在SQLServer中将数据从高版本导入低版本的方法

    一般的软件都是向下兼容的,高版本通常都是可以兼容低版本.但是如果想将高版本数据库中的数据导入到低版本中,直接采用常规的备份还原或是分离附加操作就会因为结构不同而报错. 要想实现数据从高版本到低版本,除 ...

  3. Linux ~ jenkins 直接安装

    前置条件: 1. Jenkins是由java编写的,所以最好安装java8以上的环境 开始安装: 1. 配置yum源,将jenkins导入yum源 sudo wget -O /etc/yum.repo ...

  4. python基础篇 15-常用模块:random string sorted lambda函数

    一.random import os,random,sys,time,string print(random.randint(1,10)) # 产生随机的整数 print(random.uniform ...

  5. Matlab|fastica遇到的问题

    fastica 1 安装 FastICA 在matlab代码实现以及运行结果by阳光idol 安装步骤博主阳光idol已经写的很清楚了 FastICA 在matlab代码实现.运行结果及错误调试方法 ...

  6. 许可协议 :GPL、BSD、MIT、Mozilla、Apache和LGPL

    原文摘自:https://blog.csdn.net/testcs_dn/article/details/38496107 首先借用有心人士的一张相当直观清晰的图来划分各种协议:开源许可证GPL.BS ...

  7. hdu:排列组合(指数型母函数)

    Problem Description有n种物品,并且知道每种物品的数量.要求从中选出m件物品的排列数.例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有"AB",&q ...

  8. OperationContext

    public void Add(double x, double y) { double result = x + y; ICallback callback = OperationContext.C ...

  9. 拉勾java核心类库--String类

    String类 @author :magiclx 摘要:String类是用final进行修饰的,关于字符串的一个类,其中包含很多个方法 关键词:String,常量池,正则表达式,数组转化 正文: 参考 ...

  10. HBase架构、模型、特点

    如需大数据开发整套视频(hadoop\hive\hbase\flume\sqoop\kafka\zookeeper\presto\spark):请联系QQ:1974983704 1.HBase概述 H ...