XiHan.Framework.Caching.InMemory
缓存的默认实现:有界 LRU 的字节存储与对象缓存,同时提供 caching.local@1 与 caching.distributed@1。没有显式 Provider 时进程内缓存、共享缓存、分布式锁与幂等去重都由它承担,适用于单实例部署、开发与测试。
| 项 | 内容 |
|---|---|
| 包 ID | XiHan.Framework.Caching.InMemory |
| 环层 | Ring 3(实现) |
| 能力 | 提供 caching.local@1、caching.distributed@1(role: default);依赖 kernel.hosting@1 |
| 特性入口 | InMemoryCachingFeature(清单 ID xihan.caching.inmemory) |
| 配置节 | XiHan:Caching:InMemory;被选中提供 caching.distributed 时另读 XiHan:Caching:Idempotency |
| 依赖 | XiHan.Framework.Caching.Abstractions;无第三方依赖 |
| AOT | 兼容 |
| 稳定性 | Preview |
概述
包里有两张互相独立的有界 LRU 表:
InMemoryCacheStore:字节存储,实现ICacheStore与ICacheStoreInspector。写入复制字节,读取返回不可修改的视图。InMemoryObjectCache:类型化缓存,实现ISharedCache(因而也是ICache)与ICacheInspector。直接保存对象引用、不序列化;它的Store指向同一进程的InMemoryCacheStore,供锁与幂等使用。
每张表的全部操作在一把锁内完成,批量方法一次加锁逐项处理;TryAddAsync 与比较删的检查和写入在同一把锁里,因而是原子的。过期条目在访问时清理,表满时淘汰最久未访问的条目。
特性只为被能力求解选中的能力注册契约接口:
| 被选中的能力 | 注册 |
|---|---|
caching.local | ICache → InMemoryObjectCache |
caching.distributed | ISharedCache → 同一个 InMemoryObjectCache;ICacheStore → InMemoryCacheStore;IDistributedLock(StoreDistributedLock);IDistributedCache;Idempotency 阶段的 IdempotencyMiddleware |
两个都选中时 ICache 与 ISharedCache 是同一个实例,经一个写入的值从另一个读得到。具体类型 InMemoryCacheStore、InMemoryObjectCache 与健康项总是注册。
何时使用
- 单实例部署、开发环境、测试:零外部基础设施,
Profiles.Standard已经包含它。 - 多实例部署:换 Caching.Redis 或 Caching.Remote 提供
caching.distributed,本包继续提供caching.local;需要读路径少一次往返时再加 Caching.Hybrid 顶替caching.local。
安装与启用
bash
dotnet add package XiHan.Framework.Caching.InMemory --prerelease当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。Profiles.Standard 已经装配本包;不用组合包时单独装配:
csharp
using XiHan.Framework.Caching.InMemory;
using XiHan.Framework.Hosting.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.AddXiHan(xihan => xihan.UseFeature<InMemoryCachingFeature>());配置
| 键 | 类型 | 缺省 | 说明 |
|---|---|---|---|
XiHan:Caching:InMemory:MaxEntries | 正整数 | 10000 | 每张表(字节存储、对象缓存各一张)的条目上限 |
XiHan:Caching:InMemory:DefaultTimeToLiveSeconds | 正数 | 300 | 条目没有指定过期时的存活时间 |
json
{ "XiHan": { "Caching": { "InMemory": { "MaxEntries": 50000, "DefaultTimeToLiveSeconds": 120 } } } }幂等选项见 Caching.Abstractions。非法值在装配时抛 ArgumentException。
主要类型
| 类型 | 说明 |
|---|---|
InMemoryCachingFeature | 特性入口;常量 HealthName 为 caching.inmemory.capacity |
InMemoryCachingOptions | MaxEntries、DefaultTimeToLive;FromConfiguration 从配置节读取 |
InMemoryCacheStore | 有界 LRU 字节存储;Count(含尚未清理的过期条目)、Clear() |
InMemoryObjectCache | 有界 LRU 对象缓存;Count |
InMemoryCacheHealthContributor | 健康项:任一张表满即 Degraded |
使用示例
管理界面列出对象条目并查看类型与剩余存活(对象条目不在字节存储里,ICacheStoreInspector 看不到它们):
csharp
using XiHan.Framework.Caching.Abstractions.Caches;
using XiHan.Framework.Caching.Abstractions.Stores;
public sealed class ObjectEntries(ICache cache)
{
public async ValueTask<List<CachedObjectInfo>> ListAsync(string prefix, CancellationToken cancellationToken)
{
var result = new List<CachedObjectInfo>();
if (cache is not ICacheInspector inspector)
{
return result;
}
var page = (await inspector.ListKeysAsync(new CacheKeyQuery { Prefix = prefix }, cancellationToken)).ValueOrThrow();
foreach (var key in page.Keys)
{
// 查看不读出值、不延长滑动过期;两次调用之间条目可能已过期
var info = await inspector.InspectAsync(key, cancellationToken);
if (info.TryGetValue(out var entry))
{
result.Add(entry);
}
}
return result;
}
}行为与限制
- 共享缓存、锁与幂等只在本进程内有效:多实例部署下锁不互斥、幂等键互不可见。
- 对象缓存返回写入时的同一个对象,调用方不得修改取回的对象。同一个键被写成别的类型时,按
T读取当作未命中。 MaxEntries对两张表分别计数;满时写入不被拒绝,而是淘汰最久未访问的条目。- 过期条目只在访问时清理,
Count包含尚未清理的过期条目;列键与查看会跳过它们。 - 键管理按键的序数顺序分页,续页标记是上一页最后一个键;
RemoveByPrefixAsync返回删除的条目中仍存活的条数。 - 字节存储与对象缓存都没有变更通知(不实现
ICacheChangeFeed),作为两级缓存的 L2 时,直接写 L2 不会失效 L1。
| 健康项 | 状态 |
|---|---|
caching.inmemory.capacity | 两张表任一条目数达到 MaxEntries 时 Degraded(CACHING_UNAVAILABLE_002,数据 storeEntries、cacheEntries、capacity),否则 Ready;同步计算,不起后台循环 |
相关
- 缓存与分布式锁
- Caching.Abstractions、Caching.Redis、Caching.Hybrid
- Profiles.Standard
- 规范:RFC-0006《缓存、分布式锁与幂等》;ADR-0055、ADR-0058
