Skip to content

cocache-core ​

cocache-core 在不依赖 Spring 和 Redis 的前提下实现 CoCache 契约。运行时依赖为 cocache-api、Caffeine(默认 L2)、Spring Expression(SpEL key)、kotlin-logging 和 CosId(clientId)。Guava 仅作为 compile-only 依赖,供 BloomKeyFilter 使用。

包概览 ​

包关键类型源码
me.ahoo.cacheTtlPolicy、CacheFactorycache/
me.ahoo.cache.consistencyCoherentCache、DefaultCoherentCache、CoherentCacheConfiguration、DefaultCoherentCacheFactory、InvalidationStamps(internal)、LocalCacheEvictedEventBus、NoOpCacheEvictedEventBusconsistency/
me.ahoo.cache.concurrentSingleFlightconcurrent/
me.ahoo.cache.clientCaffeineClientSideCache、MapClientSideCache、DefaultClientSideCacheFactoryclient/
me.ahoo.cache.distributedInMemoryDistributedCache、DistributedCacheFactorydistributed/
me.ahoo.cache.converterToStringKeyConverter、ExpKeyConverter、DefaultKeyConverterFactoryconverter/
me.ahoo.cache.filterBloomKeyFilterfilter/
me.ahoo.cache.annotationCoCacheMetadata(Parser)、JoinCacheMetadata(Parser)annotation/
me.ahoo.cache.proxyCacheInvocationHandler、DefaultCacheProxyFactory、CacheDelegated、CacheMetadataCapableproxy/
me.ahoo.cache.joinSimpleJoinCache、ExpJoinKeyExtractor、DefaultJoinCacheProxyFactoryjoin/
me.ahoo.cache.utilClientIdGenerator(UUID / 主机)util/

DefaultCoherentCache ​

mermaid
flowchart TD
    get["getCache(key)"] --> l2{"L2 hit,<br>not expired?"}
    l2 -->|yes| ret["return"]
    l2 -->|no| filter{"keyFilter.notExist?"}
    filter -->|yes| missing["return ttlPolicy.missing()"]
    filter -->|no| flight["SingleFlight.execute(cacheKey)"]
    flight --> stamp["stamp = stamps.current()"]
    stamp --> l1{"L1 hit?"}
    l1 -->|yes| fill["fillClientSide (stamp-guarded)"]
    l1 -->|no| load["cacheSource.load ?: missing"]
    load --> wb["writeBack L1 + L2 (stamp-guarded)"]

    style get fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style l2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style ret fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style filter fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style missing fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style flight fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style stamp fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style l1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style fill fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style load fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style wb fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
机制保证源码
SingleFlight同一实例内,同一 key 同一时刻只做一次 L1 读取 + 回源;等待者获得 leader 的值或原始异常;同 key 重入快速失败SingleFlight.kt
InvalidationStamps与该 key 任意失效(本地 evict/setCache、远端 onEvicted、onReset)重叠的写回会被跳过或撤销InvalidationStamps.kt
onReset通道(重新)订阅时清空 L2 并使全部戳失效DefaultCoherentCache.kt
close()幂等:注销订阅、关闭 L1DefaultCoherentCache.kt

CoherentCacheConfiguration ​

字段默认值说明
cacheName--事件频道与逻辑名称
clientId--用于识别自身发布的事件
keyConverter--业务 key → 存储 key
distributedCache--L1
clientSideCacheCaffeineClientSideCache.build()L2
cacheSourceCacheSource.noOp()L0
keyFilterKeyFilter.NO_OP存在性过滤
ttlPolicyTtlPolicy()(3600 / 60 / 60)命中值与负缓存的 TTL

TTL 策略 ​

mermaid
graph LR
    V["set(key, value)"] --> P{"value == null?"}
    P -->|no| T["ttlAt = now + jitter(ttl, ttlAmplitude)"]
    P -->|yes| M["ttlAt = now + missingTtl<br>(MissingValue)"]
    T --> S["CacheStore"]
    M --> S

    style V fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style P fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style T fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style M fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style S fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

抖动后的 TTL 钳为正数;把 ttl 设为 TtlAt.FOREVER 表示永不过期。时间取自 CacheClock:守护线程每 100ms 刷新的 volatile 秒值。System.currentTimeMillis() 在部分平台(如 macOS)上无法随线程扩展,而命中路径每次读取都要判断过期。

L2 实现 ​

类说明源码
CaffeineClientSideCache默认实现。build(maximumSize = 10_000, initialCapacity, expireAfterAccess);过期条目在读取时淘汰。不使用条目级 Expiry,否则每次读取都写元数据,热点 key 无法扩展CaffeineClientSideCache.kt
MapClientSideCache无界 ConcurrentHashMap;仅用于测试或 key 集合固定且较小的场景MapClientSideCache.kt

代理与 Join ​

  • CacheInvocationHandler 同时服务 @CoCache 与 @JoinCacheable 代理,解包 InvocationTargetException,并以身份语义实现 equals/hashCode。见代理与注解。
  • SimpleJoinCache 组合两个 Cache,但不持有它们的生命周期。evict(key) 只淘汰主缓存;主值为负缓存时返回 MissingValue。
mermaid
sequenceDiagram
autonumber
    participant App
    participant SJC as SimpleJoinCache
    participant F as firstCache
    participant J as joinCache
    App->>SJC: getCache(k)
    SJC->>F: getCache(k)
    alt MissingValue
        SJC-->>App: MissingValue
    else PresentValue
        SJC->>J: getCache(extract(v1))
        SJC-->>App: JoinValue (min ttlAt)
    end

相关页面 ​

基于 Apache License 2.0 发布。