ASP.NET Core - 缓存之分布式缓存

科技资讯 投稿 8200 0 评论

ASP.NET Core - 缓存之分布式缓存

与其他将缓存数据存储在单个应用服务器上的缓存方案相比,分布式缓存具有多个优势。

    在多个服务器的请求之间保持一致(一致性)。
  • 在进行服务器重启和应用部署后仍然有效。
  • 不使用本地内存。

1. 分布式缓存的使用

.NET Core 框架下对于分布式缓存的使用是基于 IDistributedCache 接口的,通过它进行抽象,统一了分布式缓存的使用方式,它对缓存数据的存取都是基于 byte[] 的。

    Get、GetAsync:如果在缓存中找到,则接受字符串键并以 byte[] 数组的形式检索缓存项。
  • Set、SetAsync:使用字符串键将项(作为 byte[] 数组)添加到缓存。
  • Refresh、RefreshAsync:根据键刷新缓存中的项,重置其可调到期超时(如果有)。
  • Remove、RemoveAsync:根据字符串键删除缓存项。

使用的时候只需要将其通过容器注入到相应的类中即可。

2. 分布式缓存的接入

这里只讲 .NET Core 下两种分布式缓存的接入和使用,一种是分布式内存缓存,一种是使用得比较广泛的 Redis。其他的在 .NET Core 框架下的使用是差不多的,仅仅只是接入的时候有点区别。当然,Redis 除了作为分布式缓存来使用,还有其他更加丰富的一些功能,后续也会找时间进行一些介绍。

2.1 基于内存的分布式缓存

分布式内存缓存是一个有用的实现:

  • 当在生产环境中使用单个服务器并且内存消耗不重要时。 实现分布式内存缓存会抽象缓存的数据存储。 如果需要多个节点或容错,它允许在未来实现真正的分布式缓存解决方案。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder(args
	.ConfigureServices(services =>
	{
		services.AddDistributedMemoryCache(;
	}
	.Build(;

host.Run(;

之后还是和内存缓存差不多的例子,演示一下缓存的存取、删除、刷新。

public interface IDistributedCacheService
{
	Task PrintDateTimeNow(;
}
public class DistributedCacheService : IDistributedCacheService
{
	public const string CacheKey = nameof(DistributedCacheService;
	private readonly IDistributedCache _distributedCache;
	public DistributedCacheService(IDistributedCache distributedCache
	{
		_distributedCache = distributedCache;
	}

	public async Task FreshAsync(
	{
		await _distributedCache.RefreshAsync(CacheKey;
	}

	public async Task PrintDateTimeNowAsync(
	{
		var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
		var cacheValue = await _distributedCache.GetAsync(CacheKey;
		if(cacheValue == null
		{
			// 分布式缓存对于缓存值的存取都是基于 byte[],所以各种对象必须先序列化为字符串,之后转换为 byte[] 数组
			cacheValue = Encoding.UTF8.GetBytes(time;
			var distributedCacheEntryOption = new DistributedCacheEntryOptions(
			{
				//AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(20,
				AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20,
				SlidingExpiration = TimeSpan.FromSeconds(3
			};
			// 存在基于字符串的存取扩展方法,内部其实也是通过 Encoding.UTF8 进行了编码
			// await _distributedCache.SetStringAsync(CacheKey, time, distributedCacheEntryOption;
			await _distributedCache.SetAsync(CacheKey, cacheValue, distributedCacheEntryOption;
		}
		time = Encoding.UTF8.GetString(cacheValue;
		Console.WriteLine("缓存时间:" + time;
		Console.WriteLine("当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
	}

	public async Task RemoveAsync(
	{
		await _distributedCache.RemoveAsync(CacheKey;
	}
}

之后,在入口文件添加以下代码,查看控制台结果是否与预想的一致:

using DistributedCacheSample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder(args
	.ConfigureServices(services =>
	{
		services.AddDistributedMemoryCache(;
		services.AddTransient<IDistributedCacheService, DistributedCacheService>(;
	}
	.Build(;

var distributedCache = host.Services.GetRequiredService<IDistributedCacheService>(;
// 第一次调用,设置缓存
Console.WriteLine("第一次调用,设置缓存";
await distributedCache.PrintDateTimeNowAsync(;
await Task.Delay(TimeSpan.FromSeconds(1;
// 未过滑动时间,数据不变
Console.WriteLine("未过滑动时间,数据不变";
await distributedCache.PrintDateTimeNowAsync(;
await Task.Delay(TimeSpan.FromSeconds(3;
// 已过滑动时间,数据改变
Console.WriteLine("已过滑动时间,数据改变";
await distributedCache.PrintDateTimeNowAsync(;
await Task.Delay(TimeSpan.FromSeconds(1;
// 未过滑动时间,手动刷新过期时间
Console.WriteLine("未过滑动时间,手动刷新过期时间";
await distributedCache.FreshAsync(;
await Task.Delay(TimeSpan.FromSeconds(2;
// 距离上一次调用此方法,已过滑动时间,但由于手动刷新过过期时间,过期时间重新计算,数据不变
Console.WriteLine("距离上一次调用此方法,已过滑动时间,但由于手动刷新过过期时间,过期时间重新计算,数据不变";
await distributedCache.PrintDateTimeNowAsync(;
await Task.Delay(TimeSpan.FromSeconds(2;
// 移除缓存
Console.WriteLine("移除缓存";
await distributedCache.RemoveAsync(;
// 原有的缓存已移除,调用此方法是重新设置缓存,数据改变
Console.WriteLine("原有的缓存已移除,调用此方法是重新设置缓存,数据改变";
await distributedCache.PrintDateTimeNowAsync(;

host.Run(;

2.2 基于 Redis 的分布式缓存

Redis 是一种开源的基于内存的非关系型数据存储,通常用作分布式缓存。在 .NET Core 框架中使用 Redis 实现分布式缓存,需要引用 Microsoft.Extensions.Caching.StackExchangeRedis Nuget 包,包中通过 AddStackExchangeRedisCache 添加 RedisCache 实例来配置缓存实现,该类基于 Redis 实现了 IDistributedCache 接口。

这里我在云服务器上通过 Docker 快速安装了 Redis,映射容器内 Redis 默认端口 6379 到主机端口 6379,并且设置了访问密码为 123456 。

docker run -d --name redis -p 6379:6379 redis --requirepass "123456"

(2 应用添加依赖包,并且通过配置服务依赖关系

Install-Package Microsoft.Extensions.Caching.StackExchangeRedis

或者通过 VS 的 Nuget 包管理工具进行安装

var host = Host.CreateDefaultBuilder(args
	.ConfigureServices(services =>
	{
		// services.AddDistributedMemoryCache(;
		services.AddStackExchangeRedisCache(opyions =>
		{
			opyions.Configuration = "xxx.xxx.xxx.xxx:6379,password=123456";
		};
	}
	.Build(;

这里只需要将原来的分布式内存缓存服务的配置切换为分布式 Redis 缓存的配置即可,其他的什么都不用改,就可以从内存缓存切换到 Redis 分布式缓存了。所以我们在日常工作的应用搭建中推荐使用基于分布式缓存方案,前期或者开发环境中可以使用基于内存的分布式缓存,后面项目的性能要求高了,可以很方便地切换到真正的分布式缓存,只需改动一行代码。

下面是一个示例进行内存缓存和 Redis 缓存的对比:

点击查看性能测试代码
[SimpleJob(RuntimeMoniker.Net60]
public class DistributedCacheService : IDistributedCacheService
{
	public const string CacheKey = nameof(DistributedCacheService;
	private readonly IDistributedCache _distributedCache;
	private readonly IDistributedCache _distributedMemoryCache;
	private readonly IMemoryCache _memoryCache;

	[Params(1000]
	public int N;

	public DistributedCacheService(
	{
		_distributedCache = new RedisCache(Options.Create(new RedisCacheOptions(
		{
			Configuration = "1.12.64.68:6379,password=123456"
		};
		_distributedMemoryCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions(;
		_memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions(;
	}

	public async Task FreshAsync(
	{
		await _distributedCache.RefreshAsync(CacheKey;
	}

	public async Task PrintDateTimeNowAsync(
	{
		var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
		var cacheValue = await _distributedCache.GetAsync(CacheKey;
		if (cacheValue == null
		{
			// 分布式缓存对于缓存值的存取都是基于 byte[],所以各种对象必须先序列化为字符串,之后转换为 byte[] 数组
			cacheValue = Encoding.UTF8.GetBytes(time;
			var distributedCacheEntryOption = new DistributedCacheEntryOptions(
			{
				//AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10,
				AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20,
				SlidingExpiration = TimeSpan.FromSeconds(3
			};
			// 存在基于字符串的存取扩展方法,内部其实也是通过 Encoding.UTF8 进行了编码
			// await _distributedCache.SetStringAsync(CacheKey, time, distributedCacheEntryOption;
			await _distributedCache.SetAsync(CacheKey, cacheValue, distributedCacheEntryOption;
		}
		time = Encoding.UTF8.GetString(cacheValue;
		Console.WriteLine("缓存时间:" + time;
		Console.WriteLine("当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
	}

	[Benchmark]
	public async Task PrintDateTimeNowWithRedisAsync(
	{
		for(var i =0; i< N; i++
		{
			var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
			var cacheValue = await _distributedCache.GetAsync(CacheKey;
			if (cacheValue == null
			{
				// 分布式缓存对于缓存值的存取都是基于 byte[],所以各种对象必须先序列化为字符串,之后转换为 byte[] 数组
				cacheValue = Encoding.UTF8.GetBytes(time;
				var distributedCacheEntryOption = new DistributedCacheEntryOptions(
				{
					//AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10,
					AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10,
					SlidingExpiration = TimeSpan.FromMinutes(5
				};
				// 存在基于字符串的存取扩展方法,内部其实也是通过 Encoding.UTF8 进行了编码
				// await _distributedCache.SetStringAsync(CacheKey, time, distributedCacheEntryOption;
				await _distributedCache.SetAsync(CacheKey, cacheValue, distributedCacheEntryOption;
			}
			time = Encoding.UTF8.GetString(cacheValue;
		}
	}
	[Benchmark]
	public async Task PrintDateTimeWithMemoryAsync(
	{
		for (var i = 0; i < N; i++
		{
			var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
			var cacheValue = await _distributedMemoryCache.GetAsync(CacheKey;
			if (cacheValue == null
			{
				// 分布式缓存对于缓存值的存取都是基于 byte[],所以各种对象必须先序列化为字符串,之后转换为 byte[] 数组
				cacheValue = Encoding.UTF8.GetBytes(time;
				var distributedCacheEntryOption = new DistributedCacheEntryOptions(
				{
					//AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10,
					AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10,
					SlidingExpiration = TimeSpan.FromMinutes(5
				};
				// 存在基于字符串的存取扩展方法,内部其实也是通过 Encoding.UTF8 进行了编码
				// await _distributedCache.SetStringAsync(CacheKey, time, distributedCacheEntryOption;
				await _distributedMemoryCache.SetAsync(CacheKey, cacheValue, distributedCacheEntryOption;
			}
			time = Encoding.UTF8.GetString(cacheValue;
		}
	}

	[Benchmark]
	public async Task PrintDateTimeWithMemoryAndRedisAsync(
	{
		for (var i = 0; i < N; i++
		{
			var cacheValue = await _memoryCache.GetOrCreateAsync(CacheKey, async cacheEntry =>
			{
				var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss";
				var redisCacheValue = await _distributedCache.GetAsync(CacheKey;
				if (redisCacheValue == null
				{
					// 分布式缓存对于缓存值的存取都是基于 byte[],所以各种对象必须先序列化为字符串,之后转换为 byte[] 数组
					redisCacheValue = Encoding.UTF8.GetBytes(time;
					var distributedCacheEntryOption = new DistributedCacheEntryOptions(
					{
						//AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10,
						AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10,
						SlidingExpiration = TimeSpan.FromMinutes(5
					};
					// 存在基于字符串的存取扩展方法,内部其实也是通过 Encoding.UTF8 进行了编码
					// await _distributedCache.SetStringAsync(CacheKey, time, distributedCacheEntryOption;
					await _distributedCache.SetAsync(CacheKey, redisCacheValue, distributedCacheEntryOption;
				}
				time = Encoding.UTF8.GetString(redisCacheValue;
				cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(20;
				return time;
			};
		}
	}

	public async Task RemoveAsync(
	{
		await _distributedCache.RemoveAsync(CacheKey;
	}
}

Program.cs 文件中只保留以下代码:

Summary summary = BenchmarkRunner.Run<DistributedCacheService>(;
Console.ReadLine(;

测试结果如下:

我们在业务中的缓存最终就是第三种方法的方式,结合内存缓存和 Redis 缓存,根本的思路就是在使用时将数据临时保存在本地,减少网络传输的消耗,并且根据实际业务情况控制内存缓存的超时时间以保持数据的一致性。


ASP.NET Core 中的分布式缓存


目录:ASP.NET Core 系列总结
上一篇:ASP.NET Core - 缓存之内存缓存(下

编程笔记 » ASP.NET Core - 缓存之分布式缓存

赞同 (39) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽