首页 > 其他分享 >ABP - 缓存模块(1)

ABP - 缓存模块(1)

时间:2023-06-02 14:13:39浏览次数:45  
标签:缓存 IDistributedCache ABP 模块 new public 分布式

1. 与 .NET Core 缓存的关系和差异

ABP 框架中的缓存系统核心包是 Volo.Abp.Caching ,而对于分布式缓存的支持,abp 官方提供了基于 Redis 的方案,需要安装 Volo.Abp.Caching.StackExchangeRedis 集成包。默认的情况下,在我们使用 ABP CLI 创建 ABP 框架模板项目的时候已经集成了这个包,我们不需要手动进行安装。

ABP 框架中的缓存系统在 ASP.NET Core的分布式缓存系统上进行扩展,从而为了使分布式缓存使用起来更加方便。理所当然的,ABP 框架的缓存系统兼容 ASP.NET Core 原生的分布式缓存使用方式,关于 ASP.NET Core 分布式缓存的使用,可以参考我之前的文章 ASP.NET Core - 缓存之分布式缓存,也可以看下官方文档 ASP.NET Core 中的分布式缓存

那么 ABP 框架中的缓存系统扩展了什么呢?ASP.NET Core 的分布式缓存又有哪些不便之处呢 ?ASP.NET Core 通过 IDistributedCache 抽象了分布式缓存的存取操作,但是有这个两个问题:

  • 它对缓存值的存取是基于 byte 数组的,而不是对象。

    这使得我们在使用分布式缓存的时候需要先将实例对象进行序列化/反序列化,之后再转码为 byte 数组。如果每个使用分布式缓存的地方都这么做将会有很多的重复冗余的代码,这是需要抽取封装的。

  • 为了能实现多个应用公用缓存,达成分布式缓存的左右,它将所有的缓存项存放在同一个 Key 池之中。

    这就可能会有问题,我们要特别注意缓存键的设置,如果开发人员不注意,很可能将一些缓存相互覆盖了,特别是共用缓存的应用比较多,或者多租户的情况下,这造成的问题可能很严重,而且很难排查。

ABP 框架的缓存系统扩展了通用的泛型接口 IDistributedCache<TCacheItem> ,泛型类型就是缓存值的类型,用于解决上面提到的问题:

  • 该接口内部实现了对缓存对象 序列化/反序列化 的逻辑。 默认使用 JSON 序列化, 我们如果有需要可以替换 依赖注入 系统中 IDistributedCacheSerializer 服务的实现来覆盖默认的方式。

  • 该接口会根据缓存中对象类型自动向缓存key添加 缓存名称 前缀。 默认缓存名是缓存对象的全类名(如果类名以CacheItem 结尾, 那么CacheItem 会被忽略,不应用到缓存名称上),开发人员也可以在缓存类上使用 CacheName 特性 设置缓存的名称.

  • 如果是多租户应用的话,它会自动将当前的租户id添加到缓存键中, 以区分不同租户的缓存项 。 如果租户之间需要共享缓存对象, 我们可以在缓存类打上 IgnoreMultiTenancy 特性,声明当前缓存不区分组合。

  • 允许为每个应用程序定义 全局缓存键前缀 , 不同的应用程序可以在共享的分布式缓存中拥有自己的隔离池.

  • 它提供了 错误容忍 机制,对分布式缓存存取过程中的异常进行了处理,例如分布式缓存服务连接失败等,避免因缓存问题导致应用出错。
    因为缓存本身就只是一种用于提升应用性能的策略,如果因为分布式缓存的问题导致应用出错,业务逻辑无法继续执行,那就得不偿失了。缓存系统应该是,就算将其从应用中摘除,也不影响应用业务逻辑正常执行的东西,只是性能可能差了点。

  • 它额外提供了 GetManyAsync 和 SetManyAsync 等方法, 支持对缓存的批量操作,可以显著提高批处理的性能。

2. Abp 缓存的使用

这里还是以控制台应用作为演示,Web 应用中使用比控制台更简单,因为 Web 应用中已经集成了缓存模块,不需要再自行引入。首先,通过以下命令生成一个 ABP 的控制台启动模板:

abp new AbpCacheSample -t console

之后,如果是使用基于内存的分布式缓存的话,只需要安装 Volo.Abp.Caching 包即可。

Install-package  Volo.Abp.Caching

之后,在项目的模块文件中添加模块依赖:

image

这里不需要再进行依赖关系的配置,因为在 AbpCacheModule模块的初始化之中已经配置好了相关的内容,通过源码可以看到:

image

当然,你也可以更高效地在项目所在的文件夹使用以下命令,省掉对模块依赖的配置。

abp add-package  Volo.Abp.Caching

如果是使用 Redis 分布式缓存的话,需要安装 Volo.Abp.Caching.StackExchangeRedis 集成包,这个包对 Microsoft.Extensions.Caching.StackExchangeRedis 进行了扩展,它简化了 Redis 缓存的配置,也是前面提到的 GetManyAsync 和SetManyAsync 等更加性能的方法的实现所在。

install-package Volo.Abp.Caching.StackExchangeRedis

同时,模块依赖改成以下这样:

image

同样的,模块初始化的时候也已经配置好 Redis 缓存使用的内容:

image

通过源码我们也可以看到,Redis 相应的配置是从配置文件中的 "Redis" 节点读取的,我们可以在appsettings.json 中添加以下内容启用 Redis 缓存:

{
  "Redis": {
	"IsEnabed": true, // 控制 Redis 分布式缓存是否启用
	"Configuration": "xxx.xxx.xxx.xxx:6379,password=123456" // Redis 连接字符串
  }
}

这里其实就是通过配置信息中的 "Redis:Configuration" 节点配置 RedisCacheOpions 选项,这个选项是微软标准的 Redi s缓存支持包中的,如果有需要,我们可以在代码中通过以下方式对该选项进行配置:

[DependsOn(
	typeof(AbpAutofacModule),
	typeof(AbpCachingStackExchangeRedisModule)
)]
public class AbpCacheSampleModule : AbpModule
{
	public override void ConfigureServices(ServiceConfigurationContext context)
	{
		Configure<RedisCacheOptions>(option =>
		{
			// ...
		});
	}
}

2.1 常规使用

首先我们定义缓存类:

/// <summary>
/// 缓存中,会以缓存类的全类名作为建,如果类名以 CacheItem 结尾,CacheItem 会被忽略
/// </summary>
// [CacheName("DateTime")] 也可以通过 CacheName 特性设置缓存键
public class DateTimeCacheItem
{
	public DateTime Now { get; set; }

	public string Name { get; set; }
}

之后,只需要在要用到的服务中注入 IDistributedCache 泛型接口即可:

public class HelloWorldService : ITransientDependency
{
	public const string CacheKey = nameof(HelloWorldService);
	private readonly IDistributedCache<DateTimeCacheItem> _distributedCache;

	public HelloWorldService(IDistributedCache<DateTimeCacheItem> distributedCache)
	{
		_distributedCache = distributedCache;
	}

	public async Task SayHelloAsync()
	{
		// 常规的 IDisctributedCache 的同名方法,不过 ABP 框架中进行了扩展
		// 使其支持泛型,可以直接存取对象
		//var cacheValue = await _distributedCache.GetAsync(CacheKey);
		//if (cacheValue == null)
		//{
		//    cacheValue = new DateTimeCacheItem
		//    {
		//        Name = CacheKey,
		//        Now = DateTime.Now,
		//    };
		//    await _distributedCache.SetAsync(
		//        CacheKey, // 缓存键,最终的键会是 缓存名称(缓存类全类名去除CacheItem,或CacheName特性设置的名称) + 这里的键
		//        cacheValue, // 直接存取对象,而不用自己序列化/反序列化 以及转码 
		//        new DistributedCacheEntryOptions // 一样可以通过选项设置缓存策略
		//    {
		//        SlidingExpiration = TimeSpan.FromMinutes(1)
		//    });
		//}

		// ABP 框架新增的方法
		var cacheValue = await _distributedCache.GetOrAddAsync(
			CacheKey,
			async () =>
			{
				return await Task.FromResult(new DateTimeCacheItem()
				{
					Name = CacheKey,
					Now = DateTime.Now
				});
			},
			() => new DistributedCacheEntryOptions
			{
				SlidingExpiration= TimeSpan.FromMinutes(1)
			});

		Console.WriteLine(JsonSerializer.Serialize(cacheValue));
		Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
	}
}

image

2.2 非字符串类型的 Key

除了可以使用 string 类型作为缓存键之外,我们还可以通过 IDistributedCache<CacheKey, CacheItem> 接口使用其他类型,甚至复杂类型作为缓存键,使用复杂类型作为缓存键的时候需要重写 ToString() 方法。

public class MyCacheKey
	{
		public int Id { get; set; }

		public string Name { get; set; }

		public override string ToString()
		{
			return Id + Name;
		}
	}

public class HelloWorldService : ITransientDependency
{
	private readonly IDistributedCache<DateTimeCacheItem, int> _distributedCacheKeyInt;
	private readonly IDistributedCache<DateTimeCacheItem, MyCacheKey> _distiributedCacheKey;

	public HelloWorldService(
		IDistributedCache<DateTimeCacheItem, int> distributedCacheKeyInt,
		IDistributedCache<DateTimeCacheItem, MyCacheKey> distiributedCacheKey)
	{
		_distributedCache = distributedCache;
	}

	public async Task SayHelloAsync()
	{
		_ = await _distributedCacheKeyInt.GetOrAddAsync(
			1,
			async () =>
			{
				return await Task.FromResult(new DateTimeCacheItem
				{
					Name = "1",
					Now = DateTime.Now
				});
			},
			() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) }
			);

		_ = await _distiributedCacheKey.GetOrAddAsync(
			new MyCacheKey { Id = 1, Name = "MyKey" },
			async () =>
			{
				return await Task.FromResult(new DateTimeCacheItem
				{
					Name = "1",
					Now = DateTime.Now
				});
			},
			() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) }
			);
	}
}

2.3 批量操作

批量进行缓存存取操作的方式如下:

_ = await _distributedCache.GetOrAddManyAsync(new List<string> { "Key1", "Key2" }, async keys =>
{
	return await Task.FromResult(
		keys.Select(k => new KeyValuePair<string, DateTimeCacheItem>(k, new DateTimeCacheItem { Name = k, Now = DateTime.Now }))
		.ToList()
		);
},
() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) });

ABP的分布式缓存接口定义了以下批量操作方法,当你需要在一个方法中调用多次缓存操作时,这些方法可以提高性能

  • SetManyAsync 和 SetMany 方法可以用来向缓存中设置多个值.
  • GetManyAsync 和 GetMany 方法可以用来从缓存中获取多个值.
  • GetOrAddManyAsync 和 GetOrAddMany 方法可以用来从缓存中获取并添加缺少的值.
  • RefreshManyAsync 和 RefreshMany 方法可以来用重置多个值的滚动过期时间.
  • RemoveManyAsync 和 RemoveMany 方法可以用来从缓存中删除多个值.

这些不是标准的ASP.NET Core缓存方法, 所以其他的分布式缓存方案可能不支持。而 ABP 通过 Volo.Abp.Caching.StackExchangeRedis 实现了 Redis 分布式缓存下的批量操作。如果采用了其他方案实现分布式缓存,而提供程序不支持的情况下,会回退到循环调用 SetAsync 和 GetAsync 方法。

以上就是 ABP 框架中缓存系统的基本使用。除此之外,ABP 框架提供了 AbpDistributedCacheOptions 选项用于配置一些缓存策略,可用属性:

  • HideErrors (bool, 默认: true): 启用/禁用隐藏从缓存服务器写入/读取值时的错误.
  • KeyPrefix (string, 默认: null): 如果你的缓存服务器由多个应用程序共同使用, 则可以为应用程序的缓存键设置一个前缀. 在这种情况下, 不同的应用程序不能覆盖彼此的缓存内容.
  • GlobalCacheEntryOptions (DistributedCacheEntryOptions): 用于设置保存缓内容却没有指定选项时, 默认的分布式缓存选项 (例如 AbsoluteExpiration 和 SlidingExpiration). SlidingExpiration的默认值设置为20分钟.

3. 额外功能

另外,还有ABP 框架下的缓存系统还有以下一些功能:

  • 错误处理

    当为你的对象设计缓存时, 通常会首先尝试从缓存中获取值. 如果在缓存中找不到该值, 则从来源查询对象. 它可能在数据库中, 或者可能需要通过HTTP调用远程服务器.

    在大多数情况下, 你希望容忍缓存错误; 如果缓存服务器出现错误, 也不希望取消该操作. 相反, 你可以默默地隐藏(并记录)错误并从来源查询. 这就是ABP框架默认的功能.

    ABP的分布式缓存异常处理, 默认记录并隐藏错误,有一个全局修改该功能的选项.所有的IDistributedCache (和 IDistributedCache<TCacheItem, TCacheKey>)方法都有一个可选的参数hideErrors, 默认值为null. 如果此参数设置为null, 则全局生效, 否则你可以选择单个方法调用时隐藏或者抛出异常.

  • 工作单元级别的缓存

    分布式缓存服务提供了一个有趣的功能. 假设你已经更新了数据库中某本书的价格, 然后将新价格设置到缓存中, 以便以后使用缓存的值. 如果设置缓存后出现异常, 并且更新图书价格的事务被回滚了, 该怎么办?在这种情况下, 缓存值是错误的.

    IDistributedCache<..>方法提供一个可选参数, considerUow, 默认为false. 如果将其设置为true, 则你对缓存所做的更改不会应用于真正的缓存存储, 而是与当前的工作单元关联. 你将获得在同一工作单元中设置的缓存值, 但仅当前工作单元成功时更改才会生效.

  • 可替换的键、值处理方式

    • IDistributedCacheSerializer

      IDistributedCacheSerializer 服务用于序列化和反序列化缓存内容. 默认实现是 Utf8JsonDistributedCacheSerializer 类, 它使用 IJsonSerializer 服务将对象转换为 JSON, 反之亦然. 然后, 它使用 UTC8 编码将 JSON 字符串转换为分布式缓存接受的字节数组.

      如果你想实现自己的序列化逻辑, 可以自己实现并替换此服务.

    • IDistributedCacheKeyNormalizer

      默认情况下, IDistributedCacheKeyNormalizer是由DistributedCacheKeyNormalizer类实现的. 它将缓存名称、应用程序缓存前缀和当前租户id添加到缓存键中. 如果需要更高级的键规范化, 可以自己实现并替换此服务.



参考文章:
ABP 官方文档 - 缓存



ABP 系列总结:
目录:ABP 系列总结
上一篇:ABP - 依赖注入(2)

标签:缓存,IDistributedCache,ABP,模块,new,public,分布式
From: https://www.cnblogs.com/wewant/p/17132954.html

相关文章

  • asyncio:python3未来并发编程主流、充满野心的模块
    https://www.cnblogs.com/traditional/p/11828780.html楔子asyncio是Python在3.5版本中正式引入的标准库,这是Python未来并发编程的主流,非常重要的一个模块。有一个Web框架叫sanic,就是基于asyncio,使用sanic可以达到匹配Go语言的并发量(有点夸张了,还是有差距的,但......
  • 3月5日周老师缓存面试题资料
    找班主任,要周老师的路线发一下面试突击班,第一天开班有6000+人在线~!!!希望大家紧张起来,今年竞争尤其激烈,新来的同学要加把劲~!今天主题:缓存面试题周老师提升面试成功率给到对方你有思想,有个人见解redis为什么快,TPS/QPS是多少,解决了项目什么问题?1,我讲的redis第一版,之后今年新讲的redi......
  • springboot多模块打包配置问题
    工程案例结构: -baidu//聚合过程   -baidu_web     //子模块web工程   -baidu_service//子模块   -baidu_config//子模块配置工程  注意事项(配置步骤):1.baidu聚......
  • spring boot 项目打包到maven仓库供其它模块使用
     在对springboot项目进行打包发布的时候发现其它springboot项目服务真正引用使用该springboot包中的类 需对打包插件做如下修改:<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</arti......
  • java map缓存数据自动过期
     packageztest;importjava.util.Date;importjava.util.HashMap;importjava.util.Iterator;importjava.util.Map;importjava.util.Timer;importjava.util.TimerTask;/***页面跳转时产生一个token*带到页面*页面保存时校验token**防止重复提交......
  • .net core Abp定时任务实现
     publicclassUseTimeJob:Job//重点是继承Job {publicreadonlyIUserCourseJobTimeService_userCourseJobTimeService;publicreadonlyIOrganizationAppService_organizationAppService;privatereadonlyIOfflineCourseLiveRecordServic......
  • Python模块 - Paramiko
    ssh是一个协议,OpenSSH是其中一个开源实现,paramiko是Python的一个库,实现了SSHv2协议(底层使用cryptography)。有了Paramiko以后,我们就可以在Python代码中直接使用SSH协议对远程服务器执行操作,而不是通过ssh命令对远程服务器进行操作。paramiko包含两个核心组件:SSHClient和SFTPCli......
  • Nginx配置隐藏模块后的.php后缀
    要在Nginx中配置隐藏框架模块后的.php后缀,并将URL重写为http://example.com/index/user/profile,请按照以下步骤进行操作:1.  打开Nginx配置文件。2.  添加以下配置,启用URL重写和模块隐藏:server{listen80;server_nameexample.com;root/pat......
  • python之struct模块处理二进制
    嵌入式开发中,有时需要对二进制流文件进行读写操作,一种方法是将二进制流文件转换为c语言数组形式。这样可以使用python的struct模块,python的struct模块可以方便的进行字节与二进制之间的相互转换。1struct模块常用的几个函数函数说明struct.pack(format,v1,v2,...)......
  • OverTheWire攻关过程-Leviathan模块5
    我们打开lv4-lv5,查看信息信息一样估计都是调试程序我们登陆服务器查看信息有一个隐藏的文件(目录)有一个文件查看类型file命令执行看看奇怪,是二进制的信息这个时候搜索引擎查询再次查询还是一样的结果验证下密码能正常登陆......