拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Smartstore 输出缓存:Donut Hole 缓存策略深度剖析

Smartstore 输出缓存:Donut Hole 缓存策略深度剖析

Smartstore 输出缓存:Donut Hole 缓存策略深度剖析

【免费下载链接】SmartstoreA modular, scalable and ultra-fast open-source all-in-one eCommerce platform built on ASP.NET Core 10项目地址: https://gitcode.com/GitHub_Trending/smar/Smartstore

Smartstore是一款基于 ASP.NET Core 的开源一体化电子商务平台,而它的**输出缓存(Output Cache)**正是平台"超快"性能的核心秘密之一。本文将带你完整剖析 Smartstore 独特的Donut Hole(甜甜圈洞)缓存策略:静态外圈如何整体缓存、动态内层如何实时替换、缓存失效又靠什么精准触发——帮助新手和普通用户快速理解这套机制背后的性能原理。

什么是 Donut Hole 缓存策略?

想象一个电商页面:顶部导航、Banner、商品网格、页脚几乎天天不变,但"购物车数量""登录用户名"却时刻在变。传统做法要么整页不缓存(慢),要么整页缓存(数据不新鲜)。

Smartstore 的Donut Hole Caching(甜甜圈洞缓存)给出了优雅的折中方案:

  • 外圈(Page):页面的非动态外层——HTML、CSS、JavaScript 及大部分静态内容,整体缓存在服务端内存、数据库或 Redis 中;
  • 内圈(Component):即"甜甜圈的洞",嵌入在静态外层中的动态区块(如搜索框、个性化推荐),每次请求实时生成;
  • 请求到达时,服务器先取缓存的外圈,再把新鲜的内圈"填"进洞里,拼出最终页面返回给用户。

这套策略既拿到了整页缓存的巨大性能收益,又保证了动态内容永远新鲜。缓存命中后无需重新执行控制器与视图渲染,响应时间可大幅降低、服务器负载显著减少,这正是 Smartstore 号称"ultra-fast"的关键所在。

📖 官方文档中对这一概念有完整说明:output-cache.md

哪些页面可以缓存?——路由级"白名单"机制

Smartstore 的输出缓存采用**显式声明(opt-in)**方式:没有任何配置时,什么都不缓存。只有被明确列出的路由才会被视为缓存候选。

缓存路由的两种形态

元素路由格式示例
整页(Full Page)[{模块}/]控制器短名/动作Catalog/Category、BlogModule/Blog/List
视图组件(View Component)vc:[{模块}/]组件短名vc:SearchBox

模块开发者只需在模块内提供一个internal的 CacheableRoutes 类,实现 ICacheableRouteProvider 接口并返回路由字符串列表即可,无需任何 DI 注册:

internal sealed class CacheableRoutes : ICacheableRouteProvider { public int Order => 0; public IEnumerable<string> GetCacheableRoutes() { yield return "Catalog/Category"; yield return "vc:SearchBox"; } }

缓存键:为什么同一页面会有多份缓存?

同一个页面在不同环境下内容不同,因此 Smartstore 会用多个环境要素共同生成缓存条目键(cache key),每种组合各存一份:

  • 页面路径与查询字符串
  • 当前语言、货币
  • 当前商店、主题
  • 全部客户角色
  • 应用版本号

多店铺、多语言、多主题的站群场景下,这正是缓存不出"串味"问题的保障。

动态"甜甜圈洞"是如何被实时替换的?

这是整个策略最精妙的部分,官方称之为Substitution(替换):

  1. 首次请求:页面正常渲染,但所有不匹配任何"组件路由"的视图组件(即动态洞)不会被写进缓存内容,而是被替换为一段JSON 元数据(包含组件信息与它的Invoke方法参数);
  2. 后续请求:服务器直接从缓存取出"带洞的外圈",解析每个洞里的 JSON 元数据,现场调用对应组件的Invoke方法,把新鲜 HTML 填回洞里,拼出最终页面。

⚠️ 这里有两条重要约束,新手务必牢记:

  • 组件Invoke方法的参数不能依赖父页面或外层视图模型(二次请求时模型已不可用),且所有参数必须可序列化为 JSON;
  • 绝不要把涉及用户个人信息的组件设为可缓存——否则 A 用户的购物车可能展示给 B 用户。

另外,后台管理页面永远不会被缓存,只有前台页面在缓存范围内。

缓存失效:实体标签让脏数据"无处可藏"

输出缓存最大的难题是:商品 A 在后台被改价或删除后,任何以任意形式展示过商品 A 的页面都必须立即失效。Smartstore 用一套"实体标签 + 观察者"体系自动化了这件事。

显示声明(Announce)与缓存标签

核心服务是 IDisplayControl。控制器只需在准备视图模型时调用一次Announce(product),系统就会为实体生成缓存标签(tag)。内置的标签规则已在 DisplayControl.cs 中预置:

  • 商品 →p5、分类 →c12、品牌 →m3、主题(新闻)→t8、菜单 →mnu1……
  • 连带关系也被覆盖:修改商品的图片、规格、捆绑子项、阶梯价,同样会打上所属商品的标签p5。

缓存条目生成时,这些标签随条目一起被缓存。此后任何实体被编辑或删除,所有携带该标签的缓存条目都会被精准清除——改价商品 A,全站涉及 A 的页面瞬间刷新,无需手动清缓存。

对于自定义实体(如博客文章),可通过DisplayControl.RegisterHandlerFor注册标签生成规则,例如为BlogPost生成b5标签。

失效观察者:复杂场景的"总开关"

IOutputCacheInvalidationObserver 是单例服务,允许模块注册两类观察者:

  • 实体观察者:监控实体变更(如"博客文章的可见性属性被修改时,按路由批量失效所有博客列表页");
  • 设置观察者:监控系统设置键(如"任何CatalogSettings.*变更时,失效相关目录页面",支持通配前缀)。

失效粒度由粗到细都有覆盖:按路由失效(InvalidateByRouteAsync)、按标签失效(InvalidateByTagAsync)、按前缀失效(InvalidateByPrefixAsync),完整接口见 IOutputCacheProvider。

逃生舱:标记请求为"不可缓存"

即使路由匹配成功,只要页面包含不宜缓存的内容,调用 IDisplayControl 的MarkRequestAsUncacheable()即可让缓存提供方放弃拦截本次请求——私密的、个性化的页面永远安全。

上手清单:模块开发者的 5 条黄金法则

  1. ✅只声明确实可缓存的路由——输出缓存是 opt-in,少即是多;
  2. ✅用户相关组件永远不缓存,防止个人数据"串用户";
  3. ✅在准备视图模型时 Announce 所有被显示的实体,让标签系统自动接管失效;
  4. ✅为自定义实体注册标签处理器(DisplayControl.RegisterHandlerFor),并处理好子实体到父实体的标签映射;
  5. ✅在启动类的BuildPipeline中注册失效观察者,覆盖设置变更这类全局影响。

核心源码导读

想进一步深入,建议按此顺序阅读:

  • 策略概念与路由规范:dev-docs/framework/platform/output-cache.md
  • 可缓存路由定义:ICacheableRouteProvider.cs
  • 显示控制与标签体系:DisplayControl.cs、IDisplayControl.cs
  • 失效观察者与缓存提供程序:IOutputCacheInvalidationObserver.cs、IOutputCacheProvider.cs

小结

Smartstore 的输出缓存并非简单"存页面",而是一套静态外圈整体缓存 + 动态内圈实时替换 + 实体标签精准失效三位一体的工程体系。Donut Hole 策略让商城首页在秒杀级流量下依然轻快如飞,而标签与观察者机制又确保商品一改,全站缓存即刻保鲜——这正是它兼顾"快"与"新"的答案。

【免费下载链接】SmartstoreA modular, scalable and ultra-fast open-source all-in-one eCommerce platform built on ASP.NET Core 10项目地址: https://gitcode.com/GitHub_Trending/smar/Smartstore

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表