<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Cache on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/cache/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Thu, 04 Sep 2025 14:16:44 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/cache/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>我开发的keCache：面向 Spring Boot 3 的 L1 &#43; L2 两级缓存框架</title>
      <link>https://ftmao.com/blog/posts/kecache-intro/</link>
      <pubDate>Thu, 04 Sep 2025 14:16:44 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/kecache-intro/</guid>
      <description>&lt;p&gt;在高并发 Java 业务里，缓存几乎是标配。但“用缓存”这件事从来不是只有“加 Redis”一种答案：本地缓存快却解决不了多机一致，Redis 能共享却多了网络与序列化开销。我最近实现一个把两者结合起来、并针对集群一致性做了不少细节处理的小而美框架 —— &lt;strong&gt;keCache&lt;/strong&gt;，现在做一次系统梳理。&lt;/p&gt;
&lt;h2 id=&#34;一kecache-是什么&#34;&gt;一、keCache 是什么&lt;/h2&gt;
&lt;p&gt;keCache 是一个基于 &lt;strong&gt;Java 17 + Spring Boot 3.x + Caffeine + Redisson&lt;/strong&gt; 的本地缓存框架，采用 &lt;strong&gt;L1（Caffeine 本地缓存）+ L2（Redis 分布式缓存）&lt;/strong&gt; 两级架构，同时提供：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;声明式注解（&lt;code&gt;@LocalCacheable&lt;/code&gt;、&lt;code&gt;@LocalCacheEvict&lt;/code&gt;、&lt;code&gt;@LocalCacheClear&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;编程式 API（&lt;code&gt;CacheTemplate&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;集群失效广播（Redisson Pub/Sub）&lt;/li&gt;
&lt;li&gt;空值保护（防止缓存穿透）&lt;/li&gt;
&lt;li&gt;数据一致性校准 SPI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的定位不是“又一个 Redis 客户端”，而是“让本地缓存能在集群环境下安全使用”的胶水层。&lt;/p&gt;
&lt;h3 id=&#34;11-整体架构&#34;&gt;1.1 整体架构&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    App[业务 Service] --&amp;gt;|注解或 API| Facade[&amp;#34;CacheTemplate / AOP 切面&amp;#34;]
    Facade --&amp;gt; L1{L1 Caffeine}
    L1 --&amp;gt;|命中| Return[返回数据]
    L1 --&amp;gt;|未命中| L2{L2 Redis}
    L2 --&amp;gt;|命中| Deserialize[Jackson 反序列化]
    Deserialize --&amp;gt; Backfill[回填 L1]
    Backfill --&amp;gt; Return
    L2 --&amp;gt;|未命中| Load[&amp;#34;执行方法 / 加载数据&amp;#34;]
    Load --&amp;gt; Write[&amp;#34;写入 L1 &amp;amp; L2&amp;#34;]
    Write --&amp;gt; Return
    PubSub[&amp;#34;Redisson Pub/Sub&amp;#34;] --&amp;gt;|失效事件| L1
    Calibration[CalibrationProvider SPI] --&amp;gt;|定时校准| L2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;二开源地址&#34;&gt;二、开源地址&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Gitee：&lt;a href=&#34;https://gitee.com/soulnew/ke-cache&#34;&gt;https://gitee.com/soulnew/ke-cache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;三为什么需要-kecache与纯-redis--纯-caffeine-的对比&#34;&gt;三、为什么需要 keCache？与纯 Redis / 纯 Caffeine 的对比&lt;/h2&gt;
&lt;p&gt;很多团队在选择缓存方案时，常常在“纯 Redis”和“纯 Caffeine”之间摇摆。keCache 试图在二者之间取得平衡。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;纯 Caffeine&lt;/th&gt;
					&lt;th&gt;纯 Redis&lt;/th&gt;
					&lt;th&gt;keCache（L1 + L2）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;读取延迟&lt;/td&gt;
					&lt;td&gt;极低（内存内）&lt;/td&gt;
					&lt;td&gt;较高（网络 + 序列化）&lt;/td&gt;
					&lt;td&gt;热数据近似 Caffeine，冷数据走 Redis&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单机内存压力&lt;/td&gt;
					&lt;td&gt;大（全部数据在本 JVM）&lt;/td&gt;
					&lt;td&gt;小（Redis 承担）&lt;/td&gt;
					&lt;td&gt;小（只有热数据在本地）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集群一致性&lt;/td&gt;
					&lt;td&gt;无，各节点独立加载&lt;/td&gt;
					&lt;td&gt;强一致&lt;/td&gt;
					&lt;td&gt;最终一致，写操作广播失效&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缓存穿透保护&lt;/td&gt;
					&lt;td&gt;需自行实现&lt;/td&gt;
					&lt;td&gt;需自行实现&lt;/td&gt;
					&lt;td&gt;内置 NullValue 短 TTL 保护&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多机共享&lt;/td&gt;
					&lt;td&gt;不支持&lt;/td&gt;
					&lt;td&gt;支持&lt;/td&gt;
					&lt;td&gt;支持（L2 Redis）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;适用场景&lt;/td&gt;
					&lt;td&gt;只读/准静态数据、单节点&lt;/td&gt;
					&lt;td&gt;写多读少、需要强一致&lt;/td&gt;
					&lt;td&gt;读多写少、可接受最终一致&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;31-相比纯-caffeine-的好处&#34;&gt;3.1 相比纯 Caffeine 的好处&lt;/h3&gt;
&lt;p&gt;Caffeine 是 JVM 内缓存的佼佼者，但它天然只服务于当前进程。在集群部署时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每台机器都要独立加载数据&lt;/strong&gt;，导致数据库在启动/预热时被打 N 次。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据更新后其他节点无法感知&lt;/strong&gt;，容易出现 A 节点已更新、B 节点仍读旧值的“ stale data ”问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存占用随节点数线性增长&lt;/strong&gt;，同样一份热数据在每个 JVM 都存一份。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;keCache 用 L2 Redis 作为共享底座：首次加载从 Redis 取，后续读请求命中本地 Caffeine；写操作时通过 Redis Pub/Sub 通知其他节点清除本地缓存。这样既保留了 Caffeine 的本地速度，又解决了多机一致性难题。&lt;/p&gt;
&lt;h3 id=&#34;32-相比纯-redis-的好处&#34;&gt;3.2 相比纯 Redis 的好处&lt;/h3&gt;
&lt;p&gt;Redis 虽然性能好，但毕竟跨进程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;网络 RTT 不可忽视&lt;/strong&gt;：高频读取时，哪怕 1ms 也会累积成可观的延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列化/反序列化开销&lt;/strong&gt;：Java 对象要先转成 JSON/二进制，再还原，CPU 和 GC 都有压力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;热点 Key 风险&lt;/strong&gt;：大促期间某些热 Key 会被反复打到同一个 Redis 节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;keCache 把最热的数据放在本地 JVM 里，只有本地未命中或更新时才访问 Redis，显著降低网络和序列化开销，也分散了 Redis 热点压力。&lt;/p&gt;
&lt;h3 id=&#34;33-方案选型速查&#34;&gt;3.3 方案选型速查&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Start([开始选缓存方案]) --&amp;gt; Q1{是否需要多机共享?}
    Q1 --&amp;gt;|否| Q2{数据是否只读 /&amp;lt;br/&amp;gt;准静态?}
    Q2 --&amp;gt;|是| Caffeine[纯 Caffeine&amp;lt;br/&amp;gt;简单高效]
    Q2 --&amp;gt;|否| Q3{能否接受最终一致?}
    Q3 --&amp;gt;|能| KeCache[keCache&amp;lt;br/&amp;gt;L1 + L2 加速]
    Q3 --&amp;gt;|否| RedisOnly[纯 Redis /&amp;lt;br/&amp;gt;DB 强一致]
    Q1 --&amp;gt;|是| Q4{写多读少 /&amp;lt;br/&amp;gt;强一致?}
    Q4 --&amp;gt;|是| RedisOnly
    Q4 --&amp;gt;|否| KeCache&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;34-一句话总结&#34;&gt;3.4 一句话总结&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;keCache 适合“读多写少、能接受最终一致、希望兼顾本地速度与集群共享”的业务场景；如果你的业务对实时一致性要求极高（如库存扣减、金融记账），建议直接走 Redis 或数据库方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;四核心能力速览&#34;&gt;四、核心能力速览&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;能力&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;声明式缓存&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@LocalCacheable&lt;/code&gt; / &lt;code&gt;@LocalCacheEvict&lt;/code&gt; / &lt;code&gt;@LocalCacheClear&lt;/code&gt;，支持 SpEL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多键映射&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;@LocalCacheable.Multi&lt;/code&gt; 一次方法返回构建多个索引 key&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;编程式 API&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;CacheTemplate.get/put/evict/clear&lt;/code&gt;，类型安全&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;二级缓存&lt;/td&gt;
					&lt;td&gt;L1 Caffeine + L2 Redis，读时先 L1 再 L2，未命中回调加载&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;空值保护&lt;/td&gt;
					&lt;td&gt;DB 不存在的 key 缓存短 TTL 的 NullValue，防止缓存穿透&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集群一致性&lt;/td&gt;
					&lt;td&gt;Redisson Pub/Sub 广播失效事件，其他 JVM 清除本地 L1&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自失效屏蔽&lt;/td&gt;
					&lt;td&gt;事件携带 &lt;code&gt;sourceId&lt;/code&gt;，本机忽略自己发出的事件，避免误清本地缓存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;广播风暴防护&lt;/td&gt;
					&lt;td&gt;远程失效事件只清 L1，不再次广播&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;数据校准&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;CalibrationProvider&lt;/code&gt; SPI，支持时间戳扫描或 CDC 扩展&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;41-读取流程&#34;&gt;4.1 读取流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Start([读请求]) --&amp;gt; L1[查 L1 Caffeine]
    L1 --&amp;gt;|命中| A[直接返回]
    L1 --&amp;gt;|未命中| L2[查 L2 Redis]
    L2 --&amp;gt;|命中| Deserialize[Jackson 反序列化]
    Deserialize --&amp;gt; Backfill[回填 L1]
    Backfill --&amp;gt; Return[返回数据]
    L2 --&amp;gt;|未命中| Method[执行真实方法]
    Method --&amp;gt; NullCheck{结果是否为 null?}
    NullCheck --&amp;gt;|是| NullVal[写入 NullValue 到 L1/L2&amp;lt;br/&amp;gt;TTL: null-value-ttl]
    NullCheck --&amp;gt;|否| RealVal[写入真实值到 L1/L2]
    NullVal --&amp;gt; ReturnNull[返回 null]
    RealVal --&amp;gt; Return&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-写入与集群失效广播流程&#34;&gt;4.2 写入与集群失效广播流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as 节点 A（写请求）
    participant DB as 数据库
    participant R as Redis（L2）
    participant P as Redis Pub/Sub
    participant B as 节点 B
    participant C as 节点 C

    A-&amp;gt;&amp;gt;DB: 更新数据
    A-&amp;gt;&amp;gt;R: 删除/覆盖 L2 Key
    A-&amp;gt;&amp;gt;P: 发布 EvictEvent（携带 sourceId）
    A-&amp;gt;&amp;gt;A: 清除本机 L1

    P-&amp;gt;&amp;gt;B: 接收 EvictEvent
    B-&amp;gt;&amp;gt;B: sourceId ≠ 本机？
    B-&amp;gt;&amp;gt;B: 清除本地 L1
    Note over B: 只清 L1，不再次广播

    P-&amp;gt;&amp;gt;C: 接收 EvictEvent
    C-&amp;gt;&amp;gt;C: sourceId ≠ 本机？
    C-&amp;gt;&amp;gt;C: 清除本地 L1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;五快速开始&#34;&gt;五、快速开始&lt;/h2&gt;
&lt;h3 id=&#34;51-引入-starter&#34;&gt;5.1 引入 Starter&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.keke&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kecache-spring-boot-starter&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0-SNAPSHOT&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;52-启用缓存&#34;&gt;5.2 启用缓存&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.keke.kecache.starter.annotation.EnableKeCache;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.springframework.boot.autoconfigure.SpringBootApplication;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@SpringBootApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@EnableKeCache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Application&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SpringApplication.&lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;(Application.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;, args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;53-基础配置&#34;&gt;5.3 基础配置&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;local-cache&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;enabled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kecache.sample&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;default-ttl&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;null-value-ttl&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;5m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;caffeine&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;default&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;maximum-size&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;expire-after-write&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;record-stats&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;address&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;redis://127.0.0.1:6379&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;broadcast-channel&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kecache:evict&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;default-ttl&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;30m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;calibration&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;enabled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;54-声明式使用示例&#34;&gt;5.4 声明式使用示例&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AccountService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;id_&amp;#39; + #accountId&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        unless &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#result == null&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AccountInfo &lt;span style=&#34;color:#a6e22e&#34;&gt;getById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadById&lt;/span&gt;(accountId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 一次查询构建多个索引 key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable.Multi&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;id_&amp;#39; + #accountId&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheable&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;nm_&amp;#39; + #result.accountName&amp;#34;&lt;/span&gt;, unless &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#result == null&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AccountInfo &lt;span style=&#34;color:#a6e22e&#34;&gt;getByIdMultiIndex&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadById&lt;/span&gt;(accountId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheEvict&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#39;id_&amp;#39; + #accountId&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;updateById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId, AccountInfo info) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;update&lt;/span&gt;(accountId, info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@LocalCacheClear&lt;/span&gt;(cacheNames &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clearAll&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;clear&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;55-编程式使用&#34;&gt;5.5 编程式使用&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AccountService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Autowired&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; CacheTemplate cacheTemplate;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AccountInfo &lt;span style=&#34;color:#a6e22e&#34;&gt;getById&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accountId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; cacheTemplate.&lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tve_account_info&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id_&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; accountId, key &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; accountDao.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadById&lt;/span&gt;(accountId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h2 id=&#34;六模块结构&#34;&gt;六、模块结构&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    Parent[kecache-parent] --&amp;gt; API[kecache-api&amp;lt;br/&amp;gt;注解 / SPI / 配置模型 / 事件]
    Parent --&amp;gt; Core[kecache-core&amp;lt;br/&amp;gt;统一门面 / Key策略 / 空值保护]
    Parent --&amp;gt; Caffeine[kecache-provider-caffeine&amp;lt;br/&amp;gt;L1 Caffeine 实现]
    Parent --&amp;gt; Redisson[kecache-provider-redisson&amp;lt;br/&amp;gt;L2 Redis + 失效广播]
    Parent --&amp;gt; Calibration[kecache-calibration&amp;lt;br/&amp;gt;数据一致性校准]
    Parent --&amp;gt; Starter[kecache-spring-boot-starter&amp;lt;br/&amp;gt;自动配置 + AOP]
    Parent --&amp;gt; Sample[kecache-spring-boot-sample&amp;lt;br/&amp;gt;示例应用 + 测试]

    Starter --&amp;gt; API
    Starter --&amp;gt; Core
    Core --&amp;gt; Caffeine
    Core --&amp;gt; Redisson
    Core --&amp;gt; Calibration&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h2 id=&#34;七关键约定写代码前必读&#34;&gt;七、关键约定（写代码前必读）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L1 key&lt;/strong&gt;：&lt;code&gt;cacheName#dataKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2 key&lt;/strong&gt;：&lt;code&gt;{namespace|cacheName}:dataKey&lt;/code&gt;（使用 hash tag 避免集群模式下 slot 分散）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认读写策略&lt;/strong&gt;：Cache-Aside&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失效广播频道&lt;/strong&gt;：默认 &lt;code&gt;kecache:evict&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列化&lt;/strong&gt;：Jackson 启用 default typing，保留类型信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;八使用注意点与避坑指南&#34;&gt;八、使用注意点与避坑指南&lt;/h2&gt;
&lt;h3 id=&#34;81-redis-是硬性依赖&#34;&gt;8.1 Redis 是硬性依赖&lt;/h3&gt;
&lt;p&gt;L2 缓存和失效广播都依赖 Redis，测试与运行前请确保 Redis 可达。运行全部测试需要本地启动 Redis：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mvn clean test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;82-集群一致性是最终一致不是实时一致&#34;&gt;8.2 集群一致性是“最终一致”，不是实时一致&lt;/h3&gt;
&lt;p&gt;本地缓存存在一致窗口：A 节点写入并广播，B 节点收到消息前仍可能读到旧值。对于库存、余额、订单状态等强一致场景，建议绕过本地缓存，直接走 Redis 或数据库。&lt;/p&gt;
&lt;h3 id=&#34;83-空值保护是把双刃剑&#34;&gt;8.3 空值保护是把双刃剑&lt;/h3&gt;
&lt;p&gt;默认对 &lt;code&gt;null&lt;/code&gt; 结果缓存 5 分钟，可防止缓存穿透。但如果业务刚刚从 DB 写入了一条新记录，而之前查询时正好命中了 NullValue，就会出现“写入后 5 分钟内查不到”的延迟。需要时可：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缩短 &lt;code&gt;null-value-ttl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在写入后主动 &lt;code&gt;@LocalCacheEvict&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对新增数据敏感的 cacheName 关闭空值保护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;84-序列化类型变更要兼容&#34;&gt;8.4 序列化类型变更要兼容&lt;/h3&gt;
&lt;p&gt;Redis 层使用 Jackson 并保留类型信息。如果缓存对象发生不兼容的类结构变更（如字段类型改变、删除字段、包名调整），旧数据反序列化可能失败。建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对缓存 DTO 做版本控制&lt;/li&gt;
&lt;li&gt;重大变更时清空 Redis 对应 key 或切换 &lt;code&gt;cacheName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;避免把接口、匿名类、动态代理对象放入缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;85-注意-key-的命名空间隔离&#34;&gt;8.5 注意 Key 的命名空间隔离&lt;/h3&gt;
&lt;p&gt;L2 key 格式为 &lt;code&gt;{namespace|cacheName}:dataKey&lt;/code&gt;。如果多个业务共用同一个 Redis 实例，务必设置不同的 &lt;code&gt;namespace&lt;/code&gt;，否则可能出现 Key 冲突。&lt;/p&gt;
&lt;h3 id=&#34;86-localcacheablemulti-的-result-引用要判空&#34;&gt;8.6 &lt;code&gt;@LocalCacheable.Multi&lt;/code&gt; 的 &lt;code&gt;result&lt;/code&gt; 引用要判空&lt;/h3&gt;
&lt;p&gt;多索引注解里如果通过 &lt;code&gt;#result.xxx&lt;/code&gt; 取字段，必须配合 &lt;code&gt;unless = &amp;quot;#result == null&amp;quot;&lt;/code&gt;，否则方法返回 null 时会触发 SpEL 计算异常。&lt;/p&gt;
&lt;h3 id=&#34;87-aop-自调用不会生效&#34;&gt;8.7 AOP 自调用不会生效&lt;/h3&gt;
&lt;p&gt;和 Spring Cache 一样，keCache 的注解基于 AOP。同类方法内部调用（&lt;code&gt;this.method()&lt;/code&gt;）不会走代理，缓存注解不会生效。需要时注入自身代理或改用编程式 &lt;code&gt;CacheTemplate&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;88-谨慎使用-localcacheclear&#34;&gt;8.8 谨慎使用 &lt;code&gt;@LocalCacheClear&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@LocalCacheClear&lt;/code&gt; 会清空整个 &lt;code&gt;cacheName&lt;/code&gt; 的本地缓存并广播。在高并发场景下，频繁全量清除会：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;导致本地缓存命中率骤降&lt;/li&gt;
&lt;li&gt;引发集体回源，DB 压力陡增&lt;/li&gt;
&lt;li&gt;产生大量广播消息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建议优先使用 &lt;code&gt;@LocalCacheEvict&lt;/code&gt; 精确驱逐。&lt;/p&gt;
&lt;h3 id=&#34;89-校准任务不是开箱即用的&#34;&gt;8.9 校准任务不是开箱即用的&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CalibrationProvider&lt;/code&gt; 目前只是 SPI 骨架，需要业务方自己实现 &lt;code&gt;fetchChanges(Instant start, Instant end)&lt;/code&gt; 逻辑（如查变更表、CDC 日志）。开启 &lt;code&gt;local-cache.calibration.enabled=true&lt;/code&gt; 后才会按 Cron 触发。&lt;/p&gt;
&lt;h3 id=&#34;810-当前-redis-部署模式限制&#34;&gt;8.10 当前 Redis 部署模式限制&lt;/h3&gt;
&lt;p&gt;自动配置里的 &lt;code&gt;RedissonClient&lt;/code&gt; 目前只实现了 &lt;strong&gt;single 单节点&lt;/strong&gt;模式。如果生产环境使用 Redis Cluster、Sentinel 或主从，需要自行扩展配置。&lt;/p&gt;
&lt;h3 id=&#34;811-关注-caffeine-内存与统计&#34;&gt;8.11 关注 Caffeine 内存与统计&lt;/h3&gt;
&lt;p&gt;默认开启 &lt;code&gt;record-stats: true&lt;/code&gt;，本地缓存命中率、加载次数等都会被记录。建议配合 actuator / Micrometer（后续可扩展）监控：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maximum-size&lt;/code&gt; 是否设置合理&lt;/li&gt;
&lt;li&gt;本地缓存命中率&lt;/li&gt;
&lt;li&gt;加载耗时与异常率&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;九许可证&#34;&gt;九、许可证&lt;/h2&gt;
&lt;p&gt;keCache 采用 &lt;strong&gt;MIT&lt;/strong&gt; 协议开源。&lt;/p&gt;
&lt;h2 id=&#34;十总结&#34;&gt;十、总结&lt;/h2&gt;
&lt;p&gt;keCache 是一个目标聚焦、实现克制的两级缓存框架：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你想要 &lt;strong&gt;Caffeine 的速度 + Redis 的共享能力&lt;/strong&gt;，它值得尝试。&lt;/li&gt;
&lt;li&gt;如果你希望 &lt;strong&gt;声明式注解即可解决集群本地缓存一致性问题&lt;/strong&gt;，它提供了失效广播与自失效屏蔽。&lt;/li&gt;
&lt;li&gt;如果你的业务对 &lt;strong&gt;实时一致性零容忍&lt;/strong&gt;，请把它当作“加速层”而非“唯一数据源”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有兴趣可以直接到 Gitee 查看源码与示例：&lt;a href=&#34;https://gitee.com/soulnew/ke-cache&#34;&gt;https://gitee.com/soulnew/ke-cache&lt;/a&gt;。&lt;/p&gt;</description>
    </item>
    
  </channel>
</rss>
