ABP VNext + 多级缓存架构:本地 + Redis + CDN
📚 目录
- [ABP VNext + 多级缓存架构:本地 + Redis + CDN](#ABP VNext + 多级缓存架构:本地 + Redis + CDN)
-
- [一、引言 🚀](#一、引言 🚀)
- [二、环境与依赖 🛠️](#二、环境与依赖 🛠️)
- [三、架构概览 🌐](#三、架构概览 🌐)
-
- [请求全链路示意 🛣️](#请求全链路示意 🛣️)
- [四、本地内存缓存层 🧠](#四、本地内存缓存层 🧠)
- [五、分布式锁提供者注册 🔐](#五、分布式锁提供者注册 🔐)
- [六、HybridCache:二级缓存一体化 🤝](#六、HybridCache:二级缓存一体化 🤝)
- [七、CDN 静态资源加速 ☁️](#七、CDN 静态资源加速 ☁️)
- [八、一致性与防护 🛡️](#八、一致性与防护 🛡️)
- [九、序列化与性能 ⚙️](#九、序列化与性能 ⚙️)
- [十、失效管理与版本控制 🔄](#十、失效管理与版本控制 🔄)
- [十一、监控与可观察性 📊](#十一、监控与可观察性 📊)
- [十二、多区域与高可用 🌍](#十二、多区域与高可用 🌍)
- [十三、自动化测试与 CI/CD 🧪](#十三、自动化测试与 CI/CD 🧪)
- [十四、端到端示例 🔧](#十四、端到端示例 🔧)
一、引言 🚀
✨ TL;DR
- 🔥 本地内存 Cache + Redis 分布式 Cache(HybridCache)+ CDN 静态资源三级缓存
- ⚙️ 演示 Cache-Aside、Write-Through、Write-Behind、分布式锁、版本管理、失效广播
- 📈 端到端示例:API 数据与静态资源协同优化
📚 背景与动机
在微服务环境中,单一内存缓存仅限单实例;纯 Redis 缓存易遭"击穿/雪崩";静态资源如不加速则带宽受限。三级缓存架构结合本地缓存的超低延迟、Redis 的跨节点共享和 CDN 的全球分发,可实现秒级响应 与高可用。
二、环境与依赖 🛠️
-
运行平台:.NET 6+ / ABP VNext 6.x
-
NuGet 包:
Microsoft.Extensions.Caching.Memory
(内存缓存)Microsoft.Extensions.Caching.StackExchangeRedis
(分布式缓存)Volo.Abp.Caching.StackExchangeRedis
(ABP Redis 扩展)Volo.Abp.DistributedLocking
+Volo.Abp.DistributedLocking.Redis
(分布式锁)Microsoft.Extensions.Caching.Hybrid
+Volo.Abp.Caching.Hybrid
(HybridCache)AspNetCore.HealthChecks.Redis
(Redis 健康检查)Prometheus.AspNetCore.HealthChecks
(Prometheus 转发)Testcontainers
+Testcontainers.Redis
(集成测试)
-
CDN 服务示例:Azure CDN / Cloudflare / AWS CloudFront
-
配置示例(appsettings.json)
jsonc"Caching": { "Memory": { "SizeLimit": 1024 }, "Redis": { "Configuration": "localhost:6379,abortConnect=false" }, "DistributedLock": { "KeyPrefix": "MyApp:" }, "HybridCache": { "GlobalHybridCacheEntryOptions": { "Expiration": "00:20:00", "LocalCacheExpiration": "00:10:00" } }, "Cdn": { "Enable": true, "BaseUrl": "https://cdn.example.com/", "StaticPaths": ["/images","/css","/js"] } }
三、架构概览 🌐
静态文件 API 请求 缓存 数据库 Client CDN 缓存 ABP 微服务 IHybridCache
(Local → Redis) 数据库
🔍 四级职责
- 🧠 本地缓存:超低延迟、单节点热点数据
- 🤝 混合缓存 :
IHybridCache<T>
自动同步本地与分布式 - 🔴 Redis:跨实例共享、高容量、高可用(由 HybridCache 隐式使用)
- ☁️ CDN:全球边缘分发、静态资源加速
请求全链路示意 🛣️
Client CDN API HybridCache Database GET /static/js/app.js 静态资源缓存 GET /api/products/123 GetOrCreate("product:123") 返回缓存数据 查询数据库 返回数据 写入本地 & Redis 返回数据 alt [HybridCache 命中] [未命中] 返回 API 数据 Client CDN API HybridCache Database
四、本地内存缓存层 🧠
📦 注入&配置
csharp
services.AddMemoryCache(options =>
{
options.SizeLimit = 1024;
});
💡 Cache-Aside 示例
csharp
if (!_memory.TryGetValue(key, out T data))
{
data = await next();
_memory.Set(key, data, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
SlidingExpiration = TimeSpan.FromMinutes(2),
Size = 1,
Priority = CacheItemPriority.High
});
}
return data;
- 线程安全 :
IMemoryCache
支持并发访问,但需注意服务生命周期
五、分布式锁提供者注册 🔐
csharp
// 在 Module.ConfigureServices 或 Startup.ConfigureServices 中:
services.AddStackExchangeRedisLocking(opts =>
{
opts.Configuration = Configuration["Caching:Redis:Configuration"];
opts.KeyPrefix = Configuration["DistributedLock:KeyPrefix"];
});
- 确保锁在多实例下跨节点生效,防止 Cache Stampede
六、HybridCache:二级缓存一体化 🤝
📦 注入 & 配置
csharp
builder.Services.AddHybridCache();
Configure<AbpHybridCacheOptions>(options =>
{
options.GlobalHybridCacheEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(20),
LocalCacheExpiration = TimeSpan.FromMinutes(10)
};
});
🔄 使用示例
csharp
var item = await _hybridCache.GetOrCreateAsync(
key,
async () => await LoadFromDbAsync(),
() => new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(20),
LocalCacheExpiration = TimeSpan.FromMinutes(10)
});
- 背后自动尝试本地→Redis→DB,更新时自动广播失效
七、CDN 静态资源加速 ☁️
⚙️ Cache-Control
csharp
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
ctx.Context.Response.Headers["Cache-Control"] = "public,max-age=31536000"
});
🚀 Azure CLI 全量失效
yaml
- run: |
az login --service-principal -u ${{ secrets.AZ_USER }} -p ${{ secrets.AZ_PASS }} --tenant ${{ secrets.AZ_TENANT }}
az cdn endpoint purge \
--resource-group MyRg \
--profile-name MyCdnProfile \
--name MyEndpoint \
--content-paths '/*'
💡 PowerShell
powershell
Clear-AzCdnEndpointContent -ResourceGroupName MyRg -ProfileName MyCdnProfile `
-EndpointName MyEndpoint -ContentPath @("/*","/css/*","/js/*")
八、一致性与防护 🛡️
是 否 是 否 Client 请求 API 本地/Hybrid 缓存命中? 直接返回 获取分布式锁 锁成功? DB 查询并写 HybridCache 回退读 DB & 写本地 写本地 & 返回
- 锁重试/回退:锁超时后,可短期自旋或直接回退 DB,保证可用性
- Write-Through/Write-Behind:按业务需求选型
九、序列化与性能 ⚙️
-
JSON vs MessagePack:
- JSON 易用但体积大
- MessagePack 紧凑高效
-
示例:
csharpservices.AddStackExchangeRedisCache(opts => { opts.Configuration = Configuration["Caching:Redis:Configuration"]; }) .AddStackExchangeRedisExtensions<MsgPackSerializer>();
十、失效管理与版本控制 🔄
-
📅 绝对 vs 滑动过期:防止缓存雪崩或冷数据长期占用
-
🎯 Tag-based Invalidation:
csharpawait _hybridCache.RemoveByTagAsync("Products");
-
🔧 版本号策略 :在 Key/URL 中嵌入版本号(如
v2
),发布时全量失效
十一、监控与可观察性 📊
📈 Health Checks & Prometheus
csharp
services.AddHealthChecks()
.AddRedis(Configuration["Caching:Redis:Configuration"], name: "Redis")
.AddCheck<MemoryHealthCheck>("Memory")
.ForwardToPrometheus();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/healthz");
endpoints.MapMetrics(); // Prometheus /metrics
});
- 需引用
Prometheus.AspNetCore.HealthChecks
🩺 OpenTelemetry & Metrics
csharp
builder.Services.AddOpenTelemetryMetrics(m =>
{
m.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddPrometheusExporter();
});
- 在
/metrics
端点采集 Cache Hit/Miss、延迟指标,用 Grafana 可视化
十二、多区域与高可用 🌍
- 被动 Geo-Replication(Premium):主从同步,仅主写
- 主动 Geo-Replication(Enterprise):多主写入,CRDT 同步
十三、自动化测试与 CI/CD 🧪
📦 Testcontainers + xUnit
csharp
public class RedisFixture : IAsyncLifetime
{
public IConnectionMultiplexer Connection { get; private set; }
private readonly RedisTestcontainer _container =
new TestcontainersBuilder<RedisTestcontainer>()
.WithDatabase(new RedisTestcontainerConfiguration { Image = "redis:7.0" })
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
.Build();
public async Task InitializeAsync()
{
await _container.StartAsync();
Connection = await ConnectionMultiplexer.ConnectAsync(_container.ConnectionString);
}
public async Task DisposeAsync()
=> await _container.DisposeAsync();
}
[CollectionDefinition("Redis")]
public class RedisCollection : ICollectionFixture<RedisFixture> { }
public class CacheTests : IClassFixture<RedisFixture>
{
private readonly IConnectionMultiplexer _conn;
public CacheTests(RedisFixture fixture) => _conn = fixture.Connection;
[Fact]
public async Task GetOrAdd_Should_Cache()
{
var db = _conn.GetDatabase();
await db.StringSetAsync("key", "value");
Assert.Equal("value", await db.StringGetAsync("key"));
}
}
🚄 CI/CD 示意:
- GitHub Actions:构建 → 单元/集成测试 → 发布 NuGet
- 静态资源打 Hash → 上传 Azure Blob →
az cdn endpoint purge
十四、端到端示例 🔧
csharp
public class ProductService : ApplicationService
{
private readonly IMemoryCache _memory;
private readonly IHybridCache<ProductDto> _hybrid;
private readonly IRepository<Product, Guid> _repo;
public ProductService(
IMemoryCache memory,
IHybridCache<ProductDto> hybrid,
IRepository<Product, Guid> repo)
{
_memory = memory;
_hybrid = hybrid;
_repo = repo;
}
public async Task<ProductDto> GetAsync(Guid id)
{
var key = $"product:{id}";
// 1️⃣ 本地缓存
if (_memory.TryGetValue(key, out ProductDto dto))
return dto;
// 2️⃣ 混合缓存 + 分布式锁
dto = await _hybrid.GetOrCreateAsync(
key,
async () => await MapAsync(await _repo.GetAsync(id)),
() => new HybridCacheEntryOptions
{ Expiration = TimeSpan.FromMinutes(10), LocalCacheExpiration = TimeSpan.FromMinutes(5) }
);
// 3️⃣ 写回本地
_memory.Set(key, dto, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
Size = 1
});
return dto;
}
}