- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
今天这篇文章来看看Masa Framework的缓存设计,上一篇文章中说到的MasaFactory的应用也会在这章节出现。文章中如有错误之处还请指点,咱们话不多说,直入主题.
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是通过redis的pub、sub发布订阅解决的(PS:这块的发布订阅官方有一个抽象,并不直接依赖redis,请往下看).
当访问层读取缓存数据时,先从内存里面获取下,如果没有则向分布式缓存获取并写入到内存缓存,并且同时开启一个关于缓存key的分布式订阅,如果收到消息则同步更新内存缓存.
当访问层写入缓存时,同时写入内存以及分布式缓存,然后再发布关于缓存key的分布式消息,其它客户端收到消息时则同步更新各自内存缓存数据.
接下来让我们来看下Masa Framework的源码设计,首先我们把源码下载下来,然后打开。下载地址: https://github.com/masastack/MASA.Framework 。
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连接、订阅、配置等初始化。继承 DistributedCacheClientBase
RedisCacheClient
:分布式缓存的redis实现,继承 RedisCacheClientBase
Masa.Contrib.Caching.MultilevelCache :多级缓存实现 。
IDistributedCacheClient
。 整个缓存组件的设计,最主要类是这些,当然还有一些option配置和帮助类,我就没有画出来,这个留待大家自己去探索 。
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
builder.Services.AddDistributedCache(opt =>
{
opt.UseStackExchangeRedisCache();
});
"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
builder.Services.AddMultilevelCache(opt =>
{
opt.UseStackExchangeRedisCache();
});
"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的程序能够读取到数据 。
其实任何语言都能实现这个多级缓存功能,我们去看框架源码,不仅是对功能原理的探索,也是学习别人的设计思想.
提升访问速度,降低分布式缓存压力:masa的多级缓存优先从内存读取数据,提高程序访问速度。间接减少网络请求,降低了分布式缓存的压力.
缓存高度扩展性:MASA Framework的缓存组件可以支持自己去实现自己的缓存逻辑,比如说目前masa的分布式缓存使用redis,我想用其它的缓存组件,或者我觉得masa实现的不优雅,完全可以自己定制.
最后此篇关于MasaFramework源码解读-02缓存模块(分布式缓存进阶之多级缓存)的文章就讲到这里了,如果你想了解更多关于MasaFramework源码解读-02缓存模块(分布式缓存进阶之多级缓存)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
MySQL表的增删改查(进阶) 1. 数据库约束 约束类型 说明 示例 NULL约束 使用NOT NULL指定列不为空 name varchar(20) not null, UNIQUE唯一约束 指定
多线程(进阶) 1. 常见的锁策略 1.1 乐观锁 悲观锁 乐观锁 : 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改数据,但是在对数据提交更新的时候,再去判断这个数据在这个期间是否有别人对
我相信在正确编码的系统中-错误(作为错误或异常)应该是不可能的(DB/memcached服务器故障导致查询失败)。我们的代码不应依赖任何假设才能正常工作,并且应尽可能地证明其正确性。 但是,为了确保我
1. 前言 泛型代码让你能根据你所定义的要求写出可以用于任何类型的灵活的、可复用的函数。你可以编写出可复用、意图表达清晰、抽象的代码。 泛型是 Swift 最强大
一、创建质量配置及关联项目 1.新建一个java代码质量配置 2.为配置添加规则 确认有4条规则了 为项目更换扫描配置 二、创建质量阈关联项目 1.
完整jenkinsfile 、sharelibrary 及jenkins配置见最后 一、gitlab push分支自动匹配 1.添加Generic Webhook插件参数,获取本次提交的分支信息
1.gitlab创建新应用 2.jenkins安装gitlab插件 3.插件安装完成后全局安全配置中使用并配置gitlab认证 4.注销重新登录后自动使用gitlab当前登录
一、部署jenkins master 1.创建Deployment YAML文件 apiVersion: apps/v1 kind: Deployment metadata: name: je
一、docker安装nexus wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum clean all
一、新建library文件 build.groovy package org.devops // 构建类型 def Build(buildType,buildShell){
一、制品获取 1.安装及配置插件 配置插件(jenkins项目中) 2.选择对应的制品 3.修改jenkins file // 新增以下代码 String artifactU
1.github创建OAuth 2.jenkins安装并配置github认证插件 jenkins配置使用github认证 3.注销重新登录
一、添加测试Maven项目 1.新建一个gitlab项目 2.导入simple-java-maven-app仓库代码(可以去github或者Gittree上都有) 3.配置mvn 国内源
一、添加AnsiColor插件 二、查看插件语法 1.打开任意pipline项目配置,找到流水线语法,并点击 跳转连接,选择插件,查看帮助 三、修改sharelibrary脚本,优
一、Pipeline概念 1 node/agent(节点) 节点是一个机器,可以是Jenkins的master节点也可以是slave节点。通过node指定当前job运行的机器(这个是脚本式语法)。
一、插件备份和恢复 1.安装备份插件 重启系统后查看 2.配置周期备份 点击进入,点击Settings Backup only builds marked to keep
一、.部署LDAP 这里使用容器部署,手动部署参考:https://www.cnblogs.com/panwenbin-logs/p/16101045.html 1.安装docker wget -
由于sonarqube开源版本不支持多分支管理,在扫描所有分支的时候都会指定同一个sonar项目,不便于我们查看 一、下载开源插件 项目地址:https://github.com/mc1arke/
一、手动测试 注意此版本已经内置包含Java语言扫描插件,不再需要单独安装 1.clone代码 git clone git@192.168.1.128:root/demo-maven-serv
我有下一种情况。 从 PHP 表单中我只获得公司 ID 我需要使用该公司 ID 排列所有用户名 我需要数组并将具有该用户名的所有日志导出到表 我的问题是,当我尝试下一步时: $sql2 = "SELE
我是一名优秀的程序员,十分优秀!