Masa Framework源码解读-02缓存模块(分布式缓存进阶之多级缓存)
序言
今天这篇文章来看看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.Caching、Masa.Contrib.Caching.Distributed.StackExchangeRedis、Masa.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连接、订阅、配置等初始化。继承DistributedCacheClientBaseRedisCacheClient:分布式缓存的redis实现,继承RedisCacheClientBase
Masa.Contrib.Caching.MultilevelCache:多级缓存实现- MultilevelCacheClient :多级缓存实现,内部依赖
IDistributedCacheClient。
- MultilevelCacheClient :多级缓存实现,内部依赖
整个缓存组件的设计,最主要类是这些,当然还有一些option配置和帮助类,我就没有画出来,这个留待大家自己去探索
Demo案例
Demo案例项目地址:https://github.com/MapleWithoutWords/masa-demos/tree/main/src/CachingDemo
上面也说到Masa Framework的缓存组件不与框架强绑定,也就是说我们可以在自己的框架中使用masa的缓存组件,下面我将展示两个项目,它们分别使用分布式缓存和多级缓存。
分布式缓存使用
- 第一步,在我们的项目中安装分布式缓存组件
Masa.Contrib.Caching.Distributed.StackExchangeRedis(下载1.0.0-preview.18版本以上),或在项目目录下使用命令行安装
dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 1.0.0-preview.18
- 第二步,在Program.cs文件中添加以下代码
builder.Services.AddDistributedCache(opt =>
{
opt.UseStackExchangeRedisCache();
});
- 第三步,在配置文件中增加以下配置。这边再补充以下,masa的redis分布式缓存是支持集群的,只需要在Servers下配置多个节点就行
"RedisOptions": {
"Servers": [
{
"Host": "127.0.0.1",
"Port": "6391"
}
],
"DefaultDatabase": 0,
"Password": "123456"
}
第四步:在构造函数中注入
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;
}
}
}
- 构造函数中注入
最终结果:注:记得启动本地redis

多级缓存使用
- 第一步,在我们的项目中安装组件:
Masa.Contrib.Caching.MultilevelCache和Masa.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
- 第二步,在Program.cs文件中添加以下代码
builder.Services.AddMultilevelCache(opt =>
{
opt.UseStackExchangeRedisCache();
});
- 第三步,在配置文件中增加以下配置。多级缓存依赖于分布式缓存,所以需要添加redis配置,如下:
"RedisOptions": {
"Servers": [
{
"Host": "127.0.0.1",
"Port": "6391"
}
],
"DefaultDatabase": 0,
"Password": "123456"
}
第四步:在构造函数中注入
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);
}
}
- 构造函数中注入
运行程序
我这边启动以命令行启动了两个服务模拟不同服务或者集群
dotnet run --urls=http://*:2001
dotnet run --urls=http://*:2002
在端口2001的程序写入数据之后,端口2002的程序能够读取到数据

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

总结
其实任何语言都能实现这个多级缓存功能,我们去看框架源码,不仅是对功能原理的探索,也是学习别人的设计思想。
提升访问速度,降低分布式缓存压力:masa的多级缓存优先从内存读取数据,提高程序访问速度。间接减少网络请求,降低了分布式缓存的压力。
缓存高度扩展性:MASA Framework的缓存组件可以支持自己去实现自己的缓存逻辑,比如说目前masa的分布式缓存使用redis,我想用其它的缓存组件,或者我觉得masa实现的不优雅,完全可以自己定制。
Masa Framework源码解读-02缓存模块(分布式缓存进阶之多级缓存)的更多相关文章
- 虎说:bootstrap源码解读(重置模块)
------<!--action-->------ 开场show:前不生“不犹豫”,后半生“不后悔”.今天又逃课,我不后悔 素材:推特公司的前端框架bootstrap(下称bt),解读源码 ...
- Vue源码学习02 初始化模块init.js
接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是 ...
- 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 ...
- Webpack探索【16】--- 懒加载构建原理详解(模块如何被组建&如何加载)&源码解读
本文主要说明Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack懒加载构建原理. 本文使用的 ...
- Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读
本文主要说明Webpack模块构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack的基础构建原理. 本文使用的W ...
- Abp 审计模块源码解读
Abp 审计模块源码解读 Abp 框架为我们自带了审计日志功能,审计日志可以方便地查看每次请求接口所耗的时间,能够帮助我们快速定位到某些性能有问题的接口.除此之外,审计日志信息还包含有每次调用接口时客 ...
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...
- seajs 源码解读
之前面试时老问一个问题seajs 是怎么加载js 文件的 在网上找一些资料,觉得这个写的不错就转载了,记录一下,也学习一下 seajs 源码解读 seajs 简单介绍 seajs是前端应用模块化开发的 ...
- SDWebImage源码解读之SDWebImagePrefetcher
> 第十篇 ## 前言 我们先看看`SDWebImage`主文件的组成模块:  "libName") 然后在"libName& ...
- mySql查询-系统公告发布接收人情况
-- display_name NZ分部所有用户SELECT user_code,display_name FROM scy_user WHERE ou_id=1627 AND is_deleted= ...
- Jenkins项目中的Performance Trend图表不显示
权限问题:chmod 777 /.../*.jtl 其中上述目录为jmeter生成jtl格式的结果报告的路径,也就是ant对应build.xml里配置好的路径.
- iOS包大小计算
一.LinkMap文件分析 说明:LinkMap数据是根据文章<LinkMap文件分析>中方法实验实测数据. 如何获得LinkMap文件 1.在XCode中开启编译选项Write Link ...
- 基于 Docker 安装 Nginx 搭建静态服务器
最近一直在准备家里的服务器部署一个自己用的网站玩玩,一来是用来学习部署的基础知识,二来,后面有空学点前端,可以部署到自己网站玩玩. 参考链接:https://juejin.cn/post/705740 ...
- 4组-Alpha冲刺-6/6
一.基本情况 队名:摸鲨鱼小队 组长博客:https://www.cnblogs.com/smallgrape/p/15574385.html 小组人数:8人 二.冲刺概况汇报 组长:许雅萍 过去两天 ...
- Unity C#代码入门
Unity C#代码入门 1. 脚本基本结构 1.1 unity生成的模板 using System.Collections; using System.Collections.Generic; us ...
- win10 wampserver升级 php7.0至 php7.2
1.去官网下载php7.2 下载地址: https://windows.php.net/download#php-7.0 2.下载安装 visual c++ 2017 或 visual c++ 20 ...
- vue echarts 多个图表自适应
<template> <div :id="id" :style="{width: `${width}`, height: `${height}`}&qu ...
- Python Boolean类型 判断
and 判断非Boolean类型数据会自动转换类型 "A" and "B" → "B" 因为表达式 A 和 B都为True所以返回 &quo ...