ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

eShopOnWeb性能优化指南:用装饰器模式为商品查询加上1分钟内存缓存

eShopOnWeb性能优化指南:用装饰器模式为商品查询加上1分钟内存缓存 eShopOnWeb性能优化指南用装饰器模式为商品查询加上1分钟内存缓存【免费下载链接】eShopOnWebSample ASP.NET Core 8.0 reference application, now community supported: https://github.com/NimblePros/eShopOnWeb项目地址: https://gitcode.com/gh_mirrors/es/eShopOnWebeShopOnWeb 是 Microsoft 官方的 ASP.NET Core 8.0 参考应用社区版它演示了一个完整的电商系统Web 商城、API 服务和 Blazor 后台管理。对于新手来说这个项目里最值得学习的性能优化点之一就是如何用装饰器模式Decorator Pattern为商品查询添加内存缓存——在不改动原有业务代码的前提下让重复的商品列表查询在 1 分钟内直接命中缓存不再反复调用 API。项目背景为什么商品查询需要缓存eShopOnWeb 的商品数据流是这样的用户浏览商城首页 → Web 应用查询商品列表、品牌、类型每次查询都要访问数据库或 API产生网络开销和数据库压力商品、品牌、类型这类数据变化频率低、读取频率高是典型的缓存好场景项目的解决思路很克制不修改原始服务类而是包一层带缓存逻辑的新类通过依赖注入把新类交给页面使用。这就是装饰器模式的经典用法。一图看懂装饰器模式装饰器模式的核心思想给已有对象动态叠加新功能而不改动它本身。请求 → [带缓存的服务] → 命中缓存── 是 → 直接返回 │ 否 ↓ [原始 HTTP 服务] → 调用 API → 结果写入缓存 → 返回它的优势✅ 原始服务代码零改动职责保持单一✅ 缓存逻辑可以独立测试、独立替换✅ 通过依赖注入DI注册切换有无缓存只需改一行注册代码项目中的 3 种缓存方案对比eShopOnWeb 在不同模块用了三种不同的缓存手段放在一起对比能帮你理解各自的适用场景方案所在模块缓存位置过期策略缓存键装饰器 内存缓存Web 商城CachedCatalogViewModelService服务器内存IMemoryCache30 秒滑动过期brands、types、items-{页}-{每页}-{品牌}-{类型}装饰器 本地存储BlazorAdmin 后台CachedCatalogItemServiceDecorator浏览器 LocalStorage1 分钟绝对过期固定键items泛型装饰器 本地存储BlazorAdmin 后台品牌/类型下拉数据浏览器 LocalStorage1 分钟绝对过期数据类名如CatalogBrand可以看到同一个商品查询在 Web 端缓存服务器内存在 Blazor 后台则缓存在浏览器端——因为 Blazor 页面是前端直接调 API 的把数据留在浏览器里能省掉整个网络往返。Web 端IMemoryCache 装饰器的关键设计Web 端的商品查询装饰器位于 CachedCatalogViewModelService.cs它包裹了原始的 CatalogViewModelService.cs对品牌、类型、分页商品列表三类查询统一加缓存。其中最有学习价值的是缓存键的设计实现在 CacheHelpers.cs缓存对象键生成规则说明商品列表items-{pageIndex}-{pageSize}-{brandId}-{typeId}每个页 筛选条件组合都有独立缓存品牌列表brands固定键类型列表types固定键为什么商品列表的键要包含页码和筛选条件因为用户筛选品牌 Nike 第 2 页和未筛选 第 1 页看到的商品完全不同如果共用一个键就会把错误的商品数据返回给页面。默认过期时长为30 秒滑动过期DefaultCacheDuration——只要持续有人访问缓存就一直续命没人访问 30 秒后自动清除。BlazorAdmin 端装饰器 浏览器本地存储后台管理端的装饰器位于 CachedCatalogItemServiceDecorator.cs思路和 Web 端一致但缓存介质换成了浏览器的 LocalStorage。它的请求流程分三步读缓存以固定键items从本地存储取出缓存条目判过期缓存条目创建时间 1 分钟 当前时间命中则直接返回否则删除旧缓存回源委托给原始的 CatalogItemService.cs 调 API拿到结果后连同创建时间一起写回本地存储这里有一个精巧的小类 CacheEntry.cs它把缓存数据 创建时间打包成一个整体让过期判断有了依据。写操作后的缓存刷新最容易遗漏的细节只缓存读取、不处理写入会导致页面展示陈旧数据。装饰器对Create/Edit/Delete三个写方法的处理方式是先委托原始服务完成真实写操作再调用RefreshLocalStorageList()删除旧缓存 → 重新拉取最新列表 → 写入新缓存这样新增商品后刷新页面这类操作永远不会看到过期数据而普通浏览操作则享受缓存加速。品牌、类型这两个下拉数据源用的是一个泛型装饰器CachedCatalogLookupDataServiceDecorator.cs用类型名自动做缓存键一份代码同时服务两种数据。依赖注入注册整个方案的开关两个装饰器最终在 ServicesConfiguration.cs 中完成注册规则很简单接口ICatalogItemService→ 绑定到装饰器类页面拿到的就是带缓存的版本具体类CatalogItemService→ 按自身类型单独注册供装饰器内部构造注入使用也就是说装饰器通过构造函数持有原始服务的实例接口调用时走装饰器装饰器内部再决定是否真正调原始服务。想临时关掉缓存把接口的绑定换回具体类即可业务代码一行都不用动。关键源码导航文件作用src/Web/Services/CachedCatalogViewModelService.csWeb 端商品查询的内存缓存装饰器src/Web/Extensions/CacheHelpers.cs缓存键生成规则与 30 秒过期配置src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.csBlazor 后台商品列表的本地存储缓存装饰器src/BlazorAdmin/Services/CacheEntry.cs缓存条目包装类数据 创建时间src/BlazorAdmin/ServicesConfiguration.cs装饰器与原始服务的 DI 注册开关常见问题FAQQ1为什么叫装饰器模式而不是直接改原服务加缓存保持单一职责。原始服务只负责取数据装饰器只负责缓存。将来想换 Redis、想加分布式锁都只需新增一个装饰器类不动老代码。Q21 分钟 / 30 秒的过期时间是怎么定的两者取向不同Web 端用滑动过期有人访问就续命适合高热度查询Blazor 端用绝对过期创建后 1 分钟必失效保证后台编辑数据后很快能看到最新状态。Q3写操作为什么必须刷新缓存缓存放的是快照。如果新增商品后不清缓存列表页会继续展示旧数据用户会以为操作没生效。装饰器里Create、Edit、Delete后统一调用刷新方法正是为了消除这个体验断层。小结eShopOnWeb 用一套组合拳演示了装饰器模式在性能优化中的标准姿势装饰器包原始服务接口不变、代码不改读操作走缓存 短过期写操作强制刷新缓存键包含全部筛选条件避免脏数据串页DI 注册处即缓存开关把这套模式迁移到你自己的项目里商品类查询的重复请求量可以大幅减少而实现成本不过百来行代码。【免费下载链接】eShopOnWebSample ASP.NET Core 8.0 reference application, now community supported: https://github.com/NimblePros/eShopOnWeb项目地址: https://gitcode.com/gh_mirrors/es/eShopOnWeb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进