Skip to content

缓存层级详解

CoCache 将数据检索组织为三个不同的层级,每个层级在缓存层次结构中承担特定角色。DefaultCoherentCache 类编排所有三个层级,通过细粒度锁和缓存一致性事件发布处理读取路径、写入路径和驱逐流程。

层级总览

mermaid
graph LR
    subgraph sg_1 ["CoCache Layers"]

        subgraph sg_2 ["L2 - ClientSideCache"]

            Guava["GuavaClientSideCache"]
            Caffeine["CaffeineClientSideCache"]
            Map["MapClientSideCache"]
        end

        subgraph sg_3 ["L1 - DistributedCache"]

            Redis["RedisDistributedCache"]
        end

        subgraph sg_4 ["L0 - CacheSource"]

            DB["Database / DataSource"]
        end
    end

    App["Application"] --> L2Label
    L2Label --> L1Label
    L1Label --> L0Label

    L2Label["L2: In-Memory<br>(fastest, per-instance)"]
    L1Label["L1: Distributed<br>(shared, Redis)"]
    L0Label["L0: Data Source<br>(authoritative)"]

    style App fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L2Label fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L1Label fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L0Label fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Guava fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Caffeine fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Map fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Redis fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style DB fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

L2 -- ClientSideCache(内存中,每实例独立)

L2 层是最快的缓存层级。它将 CacheValue<V> 条目直接存储在 JVM 堆中,以 String 为键。每个应用实例维护自己独立的 L2 缓存。

接口

ClientSideCache<V> 接口继承 Cache<String, V> 并添加 size 属性和 clear() 方法:

kotlin
interface ClientSideCache<V> : Cache<String, V> {
    val size: Long
    fun clear()
}

实现

实现底层存储配置注解源码
MapClientSideCacheConcurrentHashMap默认(无需注解)MapClientSideCache.kt
GuavaClientSideCacheGuava Cache@GuavaCacheGuavaClientSideCache.kt
CaffeineClientSideCacheCaffeine Cache@CaffeineCacheCaffeineClientSideCache.kt

GuavaClientSideCacheCaffeineClientSideCache 都提供了伴生工厂方法,用于读取各自的注解来配置 initialCapacitymaximumSizeexpireAfterWriteexpireAfterAccess 等缓存参数。ClientSideCacheFactory 抽象根据缓存接口上的注解在运行时决定使用哪个实现。

mermaid
classDiagram
    class ClientSideCache~V~ {
        <<interface>>
        +getCache(key: String) CacheValue~V~?
        +setCache(key: String, value: CacheValue~V~)
        +evict(key: String)
        +size: Long
        +clear()
    }

    class MapClientSideCache~V~ {
        -cacheMap: ConcurrentHashMap
        +getCache(key) CacheValue?
        +setCache(key, value)
        +evict(key)
    }

    class GuavaClientSideCache~V~ {
        -guavaCache: Guava Cache
        +getCache(key) CacheValue?
        +setCache(key, value)
        +evict(key)
    }

    class CaffeineClientSideCache~V~ {
        -caffeineCache: Caffeine Cache
        +getCache(key) CacheValue?
        +setCache(key, value)
        +evict(key)
    }

    class ComputedClientSideCache~V~ {
        <<interface>>
        +ttl: Long
        +ttlAmplitude: Long
    }

    ClientSideCache <|-- ComputedClientSideCache
    ComputedClientSideCache <|-- MapClientSideCache
    ComputedClientSideCache <|-- GuavaClientSideCache
    ComputedClientSideCache <|-- CaffeineClientSideCache

    style ClientSideCache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ComputedClientSideCache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style MapClientSideCache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style GuavaClientSideCache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style CaffeineClientSideCache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

L1 -- DistributedCache(共享,Redis)

L1 层是所有应用实例共享的缓存。目前主要实现是 RedisDistributedCache,基于 Spring Data Redis。

接口

DistributedCache<V> 接口继承 ComputedCache<String, V>AutoCloseable

kotlin
interface DistributedCache<V> : ComputedCache<String, V>, AutoCloseable

RedisDistributedCache

RedisDistributedCache 使用 StringRedisTemplateCodecExecutor 进行序列化。读取时,它首先通过 getExpire(key) 查询 Redis TTL 以计算本地 ttlAt 时间戳,然后获取并解码值。这确保本地表示携带 Redis 中正确的剩余 TTL。

mermaid
sequenceDiagram
autonumber
    participant CC as DefaultCoherentCache
    participant DC as RedisDistributedCache
    participant RT as StringRedisTemplate
    participant CE as CodecExecutor

    CC->>DC: getCache(key)
    DC->>RT: getExpire(key)
    RT-->>DC: ttl (seconds or -1/-2)
    DC->>DC: compute ttlAt from ttl

    alt key exists (ttl != -2)
        DC->>CE: executeAndDecode(key, ttlAt)
        CE-->>DC: CacheValue<V>
        DC-->>CC: CacheValue<V>
    else key does not exist (ttl == -2)
        DC-->>CC: null
    end

    CC->>DC: setCache(key, value)
    DC->>CE: executeAndEncode(key, value)
    CE-->>DC: done
常量含义
FOREVER-1键存在但没有过期时间
NOT_EXIST-2键在 Redis 中不存在

L0 -- CacheSource(数据源)

L0 层代表权威数据源(通常是数据库)。它是 L2 和 L1 都未命中时的最后兜底。CacheSource<K, V> 接口定义了一个方法:

kotlin
interface CacheSource<K, V> {
    fun loadCacheValue(key: K): CacheValue<V>?
}

loadCacheValue 返回 null 时,DefaultCoherentCache 存储一个 missingGuard 值以防止缓存穿透。当返回一个值时,该值被写入 L1 和 L2,并发布 CacheEvictedEvent

读取路径 -- getCache()

完整的读取路径在 DefaultCoherentCache.getCache() 和辅助方法 getL2Cache() 中实现:

mermaid
flowchart TD
    Start["getCache(key)"] --> ConvertKey["Convert key to cacheKey string"]
    ConvertKey --> L2Check1["L2: clientSideCache.getCache(cacheKey)"]

    L2Check1 -->|hit, not expired| ReturnL2["Return L2 value"]
    L2Check1 -->|hit, expired| EvictExpiredL2["Evict expired L2 entry"]
    L2Check1 -->|miss| KeyFilterCheck["KeyFilter.notExist(cacheKey)?"]

    EvictExpiredL2 --> KeyFilterCheck

    KeyFilterCheck -->|true| ReturnMissingGuard["Return missingGuard<br>(prevents cache penetration)"]
    KeyFilterCheck -->|false| L1Check["L1: distributedCache.getCache(cacheKey)"]

    L1Check -->|hit, not expired| SetL2["Copy to L2: clientSideCache.setCache()"]
    SetL2 --> ReturnL1Value["Return L1 value"]
    L1Check -->|miss or expired| AcquireLock["Acquire fine-grained lock<br>synchronized(keyLock)"]

    AcquireLock --> L2Check2["L2: getL2Cache(cacheKey)<br>(double-check after lock)"]
    L2Check2 -->|hit| ReleaseLockReturn["Release lock, return value"]
    L2Check2 -->|miss| L0Load["L0: cacheSource.loadCacheValue(key)"]

    L0Load -->|value found| WriteBoth["Write to L2 + L1<br>publish CacheEvictedEvent"]
    WriteBoth --> ReturnL0Value["Return L0 value"]

    L0Load -->|null| WriteMissingGuard["Store missingGuard in L2+L1<br>publish CacheEvictedEvent"]
    WriteMissingGuard --> ReturnNull["Return null"]

    ReleaseLockReturn --> ReleaseLock["releaseLock(cacheKey)"]

    style Start fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ConvertKey fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L2Check1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReturnL2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style EvictExpiredL2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style KeyFilterCheck fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReturnMissingGuard fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L1Check fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style SetL2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReturnL1Value fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style AcquireLock fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L2Check2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReleaseLockReturn fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L0Load fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style WriteBoth fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReturnL0Value fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style WriteMissingGuard fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReturnNull fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ReleaseLock fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

关键实现细节

细粒度锁 -- 位于第 47 行的锁映射使用 ConcurrentHashMap<String, Any>() 为每个缓存键存储一个锁对象。第 78 行getLock() 方法使用 computeIfAbsent 进行原子锁创建。L0 加载完成后,第 84 行releaseLock() 移除锁条目以防止内存泄漏。

加锁后双重检查 -- 获取锁之后,在第 104 行再次调用 getL2Cache(),检查是否有其他线程在此线程等待锁的过程中已经加载了值。这防止了冗余的 L0 调用。

写入路径 -- setCache()

位于 setCache() 的写入路径同时写入两个缓存层,然后发布驱逐事件:

kotlin
override fun setCache(key: K, value: CacheValue<V>) {
    if (value.isExpired) {
        return
    }
    val cacheKey = keyConverter.toStringKey(key)
    setCache(cacheKey, value)                    // writes to L2 + L1
    cacheEvictedEventBus.publish(CacheEvictedEvent(cacheName, cacheKey, clientId))
}

位于第 137 行的私有 setCache(cacheKey, cacheValue) 同时写入两个层级:

kotlin
private fun setCache(cacheKey: String, cacheValue: CacheValue<V>) {
    clientSideCache.setCache(cacheKey, cacheValue)   // L2
    distributedCache.setCache(cacheKey, cacheValue)   // L1
}

驱逐路径 -- evict()

位于 evict() 的驱逐路径从两个层级移除条目并发布事件:

kotlin
override fun evict(key: K) {
    val cacheKey = keyConverter.toStringKey(key)
    clientSideCache.evict(cacheKey)                    // L2
    distributedCache.evict(cacheKey)                    // L1
    cacheEvictedEventBus.publish(CacheEvictedEvent(cacheName, cacheKey, clientId))
}

事件发布触发远程实例驱逐自己 L2 缓存中相同键的条目。这是 CoCache 一致性机制的核心。完整的事件流程请参见缓存一致性

层级交互总结

mermaid
graph TB
    subgraph sg_5 ["Write Path: setCache"]

        W1["Write L2"] --> W2["Write L1"]
        W2 --> W3["Publish CacheEvictedEvent"]
    end

    subgraph sg_6 ["Eviction Path: evict"]

        E1["Evict L2"] --> E2["Evict L1"]
        E2 --> E3["Publish CacheEvictedEvent"]
    end

    subgraph sg_7 ["Read Path: getCache"]

        R1["Read L2"] --> R2["KeyFilter"]
        R2 --> R3["Read L1"]
        R3 --> R4["Lock + Load L0"]
        R4 --> R5["Write L2 + L1"]
        R5 --> R6["Publish CacheEvictedEvent"]
    end

    style W1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style W2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style W3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style R1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style R2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style R3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style R4 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style R5 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style R6 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

源码参考

文件行号说明
DefaultCoherentCache.kt50-76getL2Cache() -- L2 + KeyFilter + L1 查询
DefaultCoherentCache.kt89-135getCache() -- 带锁的完整读取路径
DefaultCoherentCache.kt142-149setCache() -- 写入路径(L2 + L1 + event)
DefaultCoherentCache.kt151-156evict() -- 驱逐路径(L2 + L1 + event)
MapClientSideCache.kt24-50基于 ConcurrentHashMap 的 L2
GuavaClientSideCache.kt26-78基于 Guava 的 L2,带注解工厂
CaffeineClientSideCache.kt27-76基于 Caffeine 的 L2,带注解工厂
CacheSource.kt24-35L0 接口
DistributedCache.kt22L1 接口
RedisDistributedCache.kt28-68Redis L1 实现
ClientSideCache.kt22-30L2 接口
KeyFilter.kt21-23布隆过滤器适配器接口
CoherentCacheConfiguration.kt26-34带默认值的配置

相关页面

基于 Apache License 2.0 发布。