ABP VNext + 多级缓存架构:本地 + Redis + CDN


📚 目录

  • ABP VNext + 多级缓存架构:本地 + Redis + CDN
    • 一、引言 🚀
    • 二、环境与依赖 🛠️
    • 三、架构概览 🌐
      • 请求全链路示意 🛣️
    • 四、本地内存缓存层 🧠
    • 五、分布式锁提供者注册 🔐
    • 六、HybridCache:二级缓存一体化 🤝
    • 七、CDN 静态资源加速 ☁️
    • 八、一致性与防护 🛡️
    • 九、序列化与性能 ⚙️
    • 十、失效管理与版本控制 🔄
    • 十一、监控与可观察性 📊
    • 十二、多区域与高可用 🌍
    • 十三、自动化测试与 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)

    "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)
数据库

🔍 四级职责

  1. 🧠 本地缓存:超低延迟、单节点热点数据
  2. 🤝 混合缓存IHybridCache<T> 自动同步本地与分布式
  3. 🔴 Redis:跨实例共享、高容量、高可用(由 HybridCache 隐式使用)
  4. ☁️ CDN:全球边缘分发、静态资源加速

请求全链路示意 🛣️

ClientCDNAPIHybridCacheDatabaseGET /static/js/app.js静态资源缓存GET /api/products/123GetOrCreate("product:123")返回缓存数据查询数据库返回数据写入本地 & Redis返回数据alt[HybridCache 命中][未命中]返回 API 数据ClientCDNAPIHybridCacheDatabase

四、本地内存缓存层 🧠

📦 注入&配置

services.AddMemoryCache(options =>
{options.SizeLimit = 1024;
});

💡 Cache-Aside 示例

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 支持并发访问,但需注意服务生命周期

五、分布式锁提供者注册 🔐

// 在 Module.ConfigureServices 或 Startup.ConfigureServices 中:
services.AddStackExchangeRedisLocking(opts =>
{opts.Configuration = Configuration["Caching:Redis:Configuration"];opts.KeyPrefix = Configuration["DistributedLock:KeyPrefix"];
});
  • 确保锁在多实例下跨节点生效,防止 Cache Stampede

六、HybridCache:二级缓存一体化 🤝

📦 注入 & 配置

builder.Services.AddHybridCache(); 
Configure<AbpHybridCacheOptions>(options =>
{options.GlobalHybridCacheEntryOptions = new HybridCacheEntryOptions{Expiration = TimeSpan.FromMinutes(20),LocalCacheExpiration = TimeSpan.FromMinutes(10)};
});

🔄 使用示例

var item = await _hybridCache.GetOrCreateAsync(key,async () => await LoadFromDbAsync(),() => new HybridCacheEntryOptions{Expiration = TimeSpan.FromMinutes(20),LocalCacheExpiration = TimeSpan.FromMinutes(10)});
  • 背后自动尝试本地→Redis→DB,更新时自动广播失效

七、CDN 静态资源加速 ☁️

⚙️ Cache-Control

app.UseStaticFiles(new StaticFileOptions
{OnPrepareResponse = ctx =>ctx.Context.Response.Headers["Cache-Control"] = "public,max-age=31536000"
});

🚀 Azure CLI 全量失效

- 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

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 紧凑高效
  • 示例

    services.AddStackExchangeRedisCache(opts =>
    {opts.Configuration = Configuration["Caching:Redis:Configuration"];
    })
    .AddStackExchangeRedisExtensions<MsgPackSerializer>();
    

十、失效管理与版本控制 🔄

  • 📅 绝对 vs 滑动过期:防止缓存雪崩或冷数据长期占用

  • 🎯 Tag-based Invalidation

    await _hybridCache.RemoveByTagAsync("Products");
    
  • 🔧 版本号策略:在 Key/URL 中嵌入版本号(如 v2),发布时全量失效


十一、监控与可观察性 📊

📈 Health Checks & Prometheus

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

builder.Services.AddOpenTelemetryMetrics(m =>
{m.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation().AddPrometheusExporter();
});
  • /metrics 端点采集 Cache Hit/Miss、延迟指标,用 Grafana 可视化

十二、多区域与高可用 🌍

  • 被动 Geo-Replication(Premium):主从同步,仅主写
  • 主动 Geo-Replication(Enterprise):多主写入,CRDT 同步

十三、自动化测试与 CI/CD 🧪

📦 Testcontainers + xUnit

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

十四、端到端示例 🔧

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;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89393.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89393.shtml
英文地址,请注明出处:http://en.pswp.cn/web/89393.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RGBA图片格式转换为RGB格式(解决convert转换的失真问题)

使用convert转换的问题 OpenCV 的 cv2.cvtColor(…, cv2.COLOR_BGRA2GRAY) 会直接忽略 Alpha 通道的含义&#xff0c;将它当作第四个颜色通道来处理。 转换公式如下&#xff1a; gray 0.114*255 0.587*0 0.299*0 ≈ 29也就是说&#xff0c;即使 Alpha 为 0&#xff08;完全透…

Spring AI之Prompt开发

文章目录1 提示词工程1_核心策略2_减少模型“幻觉”的技巧2 提示词攻击防范1_提示注入&#xff08;Prompt Injection&#xff09;2_越狱攻击&#xff08;Jailbreaking&#xff09;3 数据泄露攻击&#xff08;Data Extraction&#xff09;4 模型欺骗&#xff08;Model Manipulat…

Java面试(基础篇) - 第二篇!

未看第一篇的&#xff0c;这里可以直达 Java面试(基础篇) - 第一篇 Integer对象可以用判断吗&#xff1f;为什么&#xff1f; 回答 不可以&#xff0c;因为 比较的是对象的实例&#xff08;内存地址&#xff09;&#xff0c;Integer是有一个缓存机制的&#xff0c;它会将-1…

【C# in .NET】11. 探秘泛型:类型参数化革命

探秘泛型:类型参数化革命 泛型是 C# 和.NET框架中一项革命性的特性,它实现了 “编写一次,多处复用” 的抽象能力,同时保持了静态类型的安全性和高性能。与 C++ 模板等其他语言的泛型机制不同,.NET 泛型在 CLR(公共语言运行时)层面提供原生支持,这使得它兼具灵活性、安…

菜单权限管理

菜单管理系统的整体架构1.Menu 菜单表2.role 角色表3.role_menu 角色菜 单关联表&#xff08;多对多 &#xff09;要找role_id为3的角色能用哪个菜单:SELECT *FROM sys_menu a LEFT JOIN sys_role_menu b ON a.menu_id b.menu_id WHERE role_id3拆分开就是4.user 用户表5.user…

SQL FOREIGN KEY:详解及其在数据库设计中的应用

SQL FOREIGN KEY:详解及其在数据库设计中的应用 引言 在数据库设计中,数据完整性是至关重要的。SQL FOREIGN KEY(外键)是实现数据完整性的一种有效手段。本文将详细解释SQL FOREIGN KEY的概念、用途以及在实际数据库设计中的应用。 外键概述 1. 定义 外键(FOREIGN KE…

[yotroy.cool] 记一次 spring boot 项目宝塔面板部署踩坑

个人博客https://www.yotroy.cool/&#xff0c;感谢关注&#xff5e; 图片资源可能显示不全&#xff0c;请前往博客查看哦&#xff01;部署了个新项目&#xff0c;给我整抑郁了。。。下面是踩坑过程 宝塔面板 MySql5.7 版本 root 密码错误 这个MySQL5.7 安装完后就跑不了&#…

前端之HTML学习

HTML 学习笔记 前端三大件 HTML&#xff1a;超文本标记语言&#xff08;HyperText Markup Language&#xff09;CSS&#xff1a;层叠样式表JavaScript&#xff1a;客户端脚本语言常用框架&#xff1a;jQuery Vue 3(Element plus) HTML 基本概念 超文本&#xff1a;包含图像…

迅速高效从web2到web3转型 ,开启远程工作

Web2向Web3的转型&#xff0c;是技术、产品、组织结构和商业模式的深度变革。若要迅速且高效地完成这个转型&#xff0c;需要清晰的路径规划和战略执行。 目录 &#x1f501; 一、理解核心区别&#xff1a;Web2 vs Web3 &#x1f680; 二、转型路径 1. 选择合适的切入点 …

区块链开发协作工具全景图:从智能合约管理到去中心化治理

&#x1f4a5; 三重绞索&#xff1a;区块链开发的至暗时刻 1. 版本管理的深渊 当某DeFi团队凌晨修复漏洞时&#xff0c;发现生产环境运行的竟是两周前的废弃分支——37%的项目因代码分支混乱引发生产事故&#xff08;Electric Capital 2024&#xff09;。智能合约的版本漂移如同…

冒泡排序、选择排序、插入排序、快速排序

目录 1. 冒泡排序 (Bubble Sort) 算法思路分析 代码实现 复杂度分析 2. 选择排序 (Selection Sort) 算法思路分析 代码实现 复杂度分析 3. 插入排序 (Insertion Sort) 算法思路分析 代码实现 复杂度分析 4. 快速排序 (Quick Sort) 算法思路分析 代码实现 复杂度…

PHP语言基础知识(超详细)第一节

一. PHP简介: PHP即“超文本预处理器”,创建于1994年,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。 二. PHP的优点:…

Reloaded-II项目:解决GitHub下载Mod缺少DLL文件的问题

Reloaded-II项目&#xff1a;解决GitHub下载Mod缺少DLL文件的问题 问题现象分析 在使用Reloaded-II项目加载从GitHub下载的"Debug Stuff"模组时&#xff0c;用户遇到了一个常见的技术问题&#xff1a;系统提示缺少DLL文件&#xff0c;导致模组无法正常运行。这种情况…

0-1搭建springboot+vue的教务管理系统(核心源码)

目录 后端核心代码&#xff1a; control层 service 层 mapper层 后端核心代码&#xff1a; control层&#xff1a; classControlsImpl package com.itheima.controls.impl;import com.itheima.mapper.ClassMapper; import com.itheima.pojo.Clazz; import com.itheima.po…

Ubuntu中man手册不全解决以及man手册中英文切换方法

步入正题之前&#xff0c;先来帮助大家了解一下man手册的作用&#xff0c;让大家对其有更深的理解并充分利用一、man 手册的作用​man 手册&#xff0c;即 manual pages&#xff0c;是 Linux 系统自带的帮助文档系统。通过 man 命令&#xff0c;用户能快速获取系统中几乎所有命…

数据结构----线性表(栈及其栈的实现)C语言 学习笔记

栈&#xff1a;线性逻辑结构栈的分类 顺序栈&#xff1a;顺序存储结构实现的栈链式栈&#xff1a;链式存储结构实现的栈相关概念线性表&#xff1a;可以在任意位置操作栈&#xff1a;对线性表进行约束只能在一端插入和删除操作的线性表&#xff0c;中间不允许操作。栈底&#x…

手滑误操作? vue + Element UI 封装二次确认框 | 附源码

一诺最近在做后台管理系统时&#xff0c;遇到一个很常见但又容易被忽视的小问题&#xff1a;单选框切换时&#xff0c;用户一不小心点错&#xff0c;原有配置就没了&#xff0c;数据丢失&#xff0c;后悔也来不及。你是不是也遇到过类似的场景&#xff1f;比如切换网络模式、切…

力扣刷题367——有效的完全平方数

力扣刷题367——有效的完全平方数&#xff08;69的相似题&#xff09; 题目&#xff1a; 给你一个正整数 num 。如果 num 是一个完全平方数&#xff0c;则返回 true &#xff0c;否则返回 false 。 完全平方数 是一个可以写成某个整数的平方的整数。换句话说&#xff0c;它可以…

kubernetes架构原理与集群环境部署

kubernetes架构原理与集群环境部署概述为什么需要 KubernetesKubernetes 带来的挑战kubernetes架构解析master 节点的组件(1)API server(2)scheduler(3)Controller Manager(4)etcdNode 节点包含的组件(1)容器运行时(2)kubelet(3)kube-proxy代理kubernetes 网络插件(1)Flannel 网…

Python爬虫实战:Requests与Selenium详解

目录 一 网络爬虫的了解 1 爬虫库 urllib库 requests库 scrapy库 selenium库 2 注意&#xff01;&#xff01;&#xff01; 二 requests库 1 request库的安装 2 认识网页资源 3 获取网页资源 4 小案例 5 代理服务器 三 selenium 1 准备工作 2 应用 3 实例 一 网…