官方文档介绍:https://learn.microsoft.com/zh-cn/aspnet/core/performance/caching/overview?view=aspnetcore-9.0#hybridcache
github存储库:https://github.com/ssccinng/VideoCode/tree/master/HybridCacheTest
注意:HybirdCache在此刻(2024年11月20日)还处在预览版状态,正式版后续会随着.NET9的次要版本推出一起推出,本文也会在正式发布后再次更新一版
在介绍HybirdCache之前呢,先简单介绍一下 IDistributedCache 和 IMemoryCache。这两个是了解HybirdCache之前最好需要了解内容
![]()
当然最最最之前我们简单介绍一下缓存。
缓存有什么用呢?例如HTTP,对于一些请求操作很昂贵(耗时,耗资源)的行为,我们可以在完成一次操作后存下我们的结果,再一次访问的时候,只要拿着相同的数据得出的key,我们就能快速拿到之前获取的内容,而不需要再次进行昂贵的操作。

那我后台数据内容更新了怎么办?
当然,我们考虑到了这一点。我们可以选择当数据更新的时候手动刷新缓存中的数据,或者我们可以给我们的数据设置一个期限。当数据已经过期了的时候,如果有请求再次需要这个资源。我们就可以再去通过昂贵的操作获取一次最新的内容重新缓存起来,这样我们就实现了数据的更新。没错,这个就像是算法中所说的记忆化的概念,是类似的工作。

但如果后面一直没有请求去访问这个数据了呢?不如说这样缓存一定会越来越多吧
这么考虑是对的,所以通常缓存会有一些机制在尽量不影响性能的前提下,后台定时去清理这些已经过期的数据,这样我们的缓存就完成了。
IMemoryCache(内存中缓存)
解决了什么是缓存的问题,接下来就是存储在哪里的问题了。最简单,效率最高的做法当然是存在内存里了,存的快读的快,何乐而不为?
注入IMemoryCache之后,我们就可以在代码中这么使用缓存
if (!(memoryCache.TryGetValue(key, out object? untyped)
&& untyped is SomeData value))
{
value = GetSomeData();
memoryCache.Set(key, value, Expiration);
}
这和我们刚刚的定义基本没有偏差。那我们还需要其他类型的缓存吗?
当然需要
思考几个问题
缓存在内存,若是我们的服务器重启了怎么办?
如果程序所在的服务器内存本来就已经不够用了怎么办?
如果我们有多个服务器想要共享缓存怎么办?// 缓存内容会被影响的可能性存在吗
思考了以上几个问题后,答案呼之欲出。我们需要一种能“持久化”存储缓存的手段,或是说需要有办法脱离本机的束缚的缓存
IDistributedCache (分布式缓存)
一般来说,这类缓存用于多个服务器共享缓存(redis等等)。不过,如果你只是希望缓存在本地服务器,或者甚至是以文件的形式缓存也是可以的。(甚至可以模拟IMemoryCache,(比如AddDistributedMemoryCache))。
通常来说,在使用IDistributedCache的时候,需要自己定义序列化和反序列化器,相对纯内存的缓存会稍有局限,同时耗时也会随着不同的实现而增加。获得的好处就是我们问题的答案,重启后依然有效,无需部署在本机,多个服务器可以公用缓存。
使用方式如下
var bytes = await distributedCache.GetAsync(key, cancellationToken);
SomeData value;
if (bytes is null)
{
value = GetSomeData();
bytes = Serialize(value); // 序列化
await distributedCache.SetAsync(key, bytes,
new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)}
, cancellationToken); // 缓存五秒
}
else
{
value = Deserialize<SomeData>(arr); // 反序列化
}
return value;
虽然我们获得了一定的好处,但使用方式相比IMemoryCache要麻烦了不少,并且也有一定缺陷。例如如果获取还没完成就再次出现了请求。也会再次重复的去调用昂贵的操作(踩踏)。而且我们也不是所有时候都会想去使用分布式存储,内存缓存当然有他优越的地方(方便,快),看起来我们似乎没有办法很好的使用两种缓存
于是
HybirdCache
在.NET9中推出了HybirdCache这一类型,这其实也不是特别新潮的概念,不过官方支持了当然是极好的。
值得注意的是由于目前还是预览版 需要一些声明才可以使用
#pragma warning disable EXTEXP0018 // experimental (pre-release)
builder.Services.AddHybridCache();
#pragma warning restore EXTEXP0018 // experimental (pre-release)
HybirdCache结合了IMemoryCache与IDistributedCache,并提供了一些,这两者兼不具备的好处。
HybirdCache会检查程序有没有IDistributedCache的实现,如果有,他便会使用现有的缓存
统一的API
HybirdCache使用同一套API管理了两种内存,通过配置可以打成自动的切换,不需要手动管理,大部分时候仅仅需要使用一个函数就足以满足需求
var data = await cache.GetOrCreateAsync(
$"SomeData/{id}",
async cancellation =>
{
// valuetask注意
await Task.Delay(2500);
return new SomeData[]
{
new(1, "One"),
new(2, "Two"),
new(3, "Three")
};
},
cancellationToken: cancellationToken);
return data;
踩踏防护
如果有多个请求同时在没有缓存的时候请求了资源,HybirdCache会自动的管理请求的行为,当最早的获取到了数据后会全部一起返回,不会有重复的昂贵请求的行为。


序列化
HybirdCache的序列化有默认实现并且是可配置的,默认对string会采用byte[],其余则使用System.Text.Json序列化。如果要配置,则可以使用下面的代码完成这件事
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(10),
LocalCacheExpiration = TimeSpan.FromSeconds(5)
};
}).AddSerializer<SomeProtobufMessage,
GoogleProtobufSerializer<SomeProtobufMessage>>();
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(10),
LocalCacheExpiration = TimeSpan.FromSeconds(5)
};
}).AddSerializerFactory<GoogleProtobufSerializerFactory>();
代码中设置的Expiration与LocalCacheExpiration 分别代表着总过期时长和内存中缓存过期时长(即内存过期则会使用分布式缓存)
如此,我们便可以愉快的使用HybridCache了
学会了吗?学会了
简单试图(?

public class FileCache : IDistributedCache
{
private readonly string _cacheFilePath;
private readonly TimeSpan _defaultExpiration;
public FileCache(string cacheFilePath = "localcache.json", TimeSpan? defaultExpiration = null)
{
_cacheFilePath = cacheFilePath;
_defaultExpiration = defaultExpiration ?? TimeSpan.FromMinutes(30);
}
private Dictionary<string, CacheItem> LoadCache()
{
if (File.Exists(_cacheFilePath))
{
var json = File.ReadAllText(_cacheFilePath);
return JsonSerializer.Deserialize<Dictionary<string, CacheItem>>(json) ?? new Dictionary<string, CacheItem>();
}
return new Dictionary<string, CacheItem>();
}
private void SaveCache(Dictionary<string, CacheItem> cache)
{
var json = JsonSerializer.Serialize(cache, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_cacheFilePath, json);
}
public byte[] Get(string key)
{
var cache = LoadCache();
if (cache.ContainsKey(key))
{
var cacheItem = cache[key];
if (cacheItem.Expiration > DateTime.UtcNow)
{
return Convert.FromBase64String(cacheItem.Value);
}
else
{
cache.Remove(key); // Remove expired item
SaveCache(cache);
}
}
return null;
}
public Task<byte[]?> GetAsync(string key, CancellationToken token = new CancellationToken())
{
return Task.FromResult<byte[]?>(Get(key));
}
public async Task<byte[]> GetAsync(string key)
{
return await Task.FromResult(Get(key));
}
public Task RemoveAsync(string key, CancellationToken token = new CancellationToken())
{
Remove(key);
return Task.CompletedTask;
}
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
var cache = LoadCache();
var cacheItem = new CacheItem
{
Value = Convert.ToBase64String(value),
Expiration = DateTime.UtcNow.Add(options.AbsoluteExpirationRelativeToNow??_defaultExpiration)
};
cache[key] = cacheItem;
SaveCache(cache);
}
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options,
CancellationToken token = new CancellationToken())
{
Set(key, value, options);
return Task.CompletedTask;
}
public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options)
{
Set(key, value, options);
await Task.CompletedTask;
}
public void Refresh(string key)
{
var cache = LoadCache();
if (cache.ContainsKey(key))
{
var cacheItem = cache[key];
cacheItem.Expiration = DateTime.UtcNow.Add(_defaultExpiration);
cache[key] = cacheItem;
SaveCache(cache);
}
}
public Task RefreshAsync(string key,
CancellationToken token = new CancellationToken())
{
Refresh(key);
return Task.CompletedTask;
}
public async Task RefreshAsync(string key)
{
Refresh(key);
await Task.CompletedTask;
}
public void Remove(string key)
{
var cache = LoadCache();
if (cache.ContainsKey(key))
{
cache.Remove(key);
SaveCache(cache);
}
}
public async Task RemoveAsync(string key)
{
Remove(key);
await Task.CompletedTask;
}
// Cache item helper class
private class CacheItem
{
public string Value { get; set; }
public DateTime Expiration { get; set; }
}
}
微信公众号搜索:scixing的炼丹炉

2428

被折叠的 条评论
为什么被折叠?



