# 缓存架构 **本文档引用的文件** - [CachedVersionedEntityRepository.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/CachedVersionedEntityRepository.java) - [VersionedCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCache.java) - [VersionedCaffeineCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCaffeineCache.java) - [VersionedRedisCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java) - [JCPPCaffeineCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPCaffeineCacheConfiguration.java) - [JCPPRedisCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPRedisCacheConfiguration.java) - [PileCacheKey.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java) - [PileCacheEvictEvent.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheEvictEvent.java) - [PileRepositoryImpl.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java) - [Pile.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java) ## 目录 1. [引言](#引言) 2. [多级缓存架构设计](#多级缓存架构设计) 3. [缓存一致性机制](#缓存一致性机制) 4. [缓存读写流程](#缓存读写流程) 5. [缓存键设计](#缓存键设计) 6. [缓存失效策略](#缓存失效策略) 7. [性能监控与优化](#性能监控与优化) 8. [结论](#结论) ## 引言 JChargePointProtocol系统采用多级缓存架构来优化充电桩相关数据的访问性能。该架构结合了Caffeine本地缓存和Redis分布式缓存的优势,在保证高性能的同时确保数据一致性。本文档深入分析这一缓存架构的设计原理、实现机制和最佳实践。 ## 多级缓存架构设计 ```mermaid graph TD Client[客户端请求] --> ReadCache[读取缓存] ReadCache --> CheckCaffeine{检查Caffeine
本地缓存} CheckCaffeine --> |命中| ReturnFromCaffeine[返回Caffeine数据] CheckCaffeine --> |未命中| CheckRedis{检查Redis
分布式缓存} CheckRedis --> |命中| ReturnFromRedis[返回Redis数据] CheckRedis --> |未命中| QueryDB[查询数据库] QueryDB --> UpdateRedis[更新Redis缓存] UpdateRedis --> UpdateCaffeine[更新Caffeine缓存] UpdateCaffeine --> ReturnFromDB[返回数据库数据] WriteOperation[写入操作] --> UpdateDB[更新数据库] UpdateDB --> DeleteRedis[删除Redis缓存] DeleteRedis --> DeleteCaffeine[删除Caffeine缓存] DeleteCaffeine --> Complete[操作完成] style ReadCache fill:#f9f,stroke:#333 style WriteOperation fill:#f9f,stroke:#333 ``` **图示来源** - [PileRepositoryImpl.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java#L35-L45) - [VersionedCaffeineCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCaffeineCache.java#L30-L50) - [VersionedRedisCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java#L80-L100) **本节来源** - [JCPPCaffeineCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPCaffeineCacheConfiguration.java) - [JCPPRedisCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPRedisCacheConfiguration.java) ### Caffeine与Redis组合的优势 JChargePointProtocol采用Caffeine(本地缓存)和Redis(分布式缓存)的组合设计,主要基于以下考虑: 1. **性能分层**:Caffeine作为第一级缓存,提供微秒级的访问速度,减少对远程Redis的访问压力。 2. **高可用性**:即使Redis服务暂时不可用,本地缓存仍能提供一定程度的服务能力。 3. **网络开销优化**:避免频繁的网络往返,特别适合高并发场景下的充电桩状态查询。 4. **资源利用**:充分利用应用服务器的内存资源,同时通过Redis实现多实例间的数据共享。 Caffeine缓存通过`JCPPCaffeineCacheConfiguration`配置,支持基于权重的最大容量限制和写后过期策略。Redis缓存通过 `JCPPRedisCacheConfiguration`配置,提供了连接池管理、序列化配置和分布式环境下的缓存管理。 ## 缓存一致性机制 ```mermaid classDiagram class VersionedCache { +get(K key) +put(K key, V value) +evict(K key) +evict(K key, Integer version) +getVersion(V value) } class VersionedCaffeineCache { -doGet(K key) -doPut(K key, V value, Integer version) -wrapValue(V value, Integer version) } class VersionedRedisCache { -SET_VERSIONED_VALUE_LUA_SCRIPT -doPut(rawKey, value, version, expiration, connection) -init() } class HasVersion { +getVersion() +setVersion(Integer version) } class Pile { -UUID id -String pileCode -Integer version } class PileCacheKey { -UUID pileId -String pileCode -isVersioned() } VersionedCache <|-- VersionedCaffeineCache VersionedCache <|-- VersionedRedisCache HasVersion <|-- Pile VersionedCacheKey <|-- PileCacheKey VersionedCaffeineCache --> JCPPPair : "包装版本和值" VersionedRedisCache --> LUA : "使用Lua脚本" Pile --> PileCacheKey : "通过ID或编码创建键" ``` **图示来源** - [VersionedCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCache.java) - [VersionedCaffeineCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCaffeineCache.java) - [VersionedRedisCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java) - [Pile.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java#L60) - [PileCacheKey.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java) **本节来源** - [CachedVersionedEntityRepository.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/CachedVersionedEntityRepository.java) - [VersionedCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCache.java) ### CachedVersionedEntityRepository实现机制 `CachedVersionedEntityRepository`是缓存一致性机制的核心实现,其工作原理如下: 1. **版本号机制**:实体类(如`Pile`)实现`HasVersion`接口,包含`version`字段,每次更新时版本号递增。 2. **条件更新**:在向缓存写入数据时,只有当新数据的版本号大于缓存中现有数据的版本号时,才会执行更新操作。 3. **原子性保证**:Redis层面使用Lua脚本确保"检查版本-更新值"操作的原子性,避免并发更新导致的数据不一致。 在`VersionedRedisCache`中,通过Lua脚本实现了版本控制逻辑: - 获取当前缓存值的版本号(前8字节) - 比较新版本号与当前版本号 - 仅当新版本号更大时才更新缓存 这种设计有效避免了脏读问题,确保了缓存与数据库之间的一致性。 ## 缓存读写流程 ```mermaid sequenceDiagram participant Client as 客户端 participant Repo as PileRepositoryImpl participant Caffeine as Caffeine缓存 participant Redis as Redis缓存 participant DB as 数据库 Client->>Repo : findPileByCode(pileCode) Repo->>Caffeine : get(PileCacheKey) Caffeine-->>Repo : 缓存命中/未命中 alt Caffeine命中 Repo-->>Client : 返回数据 else Caffeine未命中 Repo->>Redis : get(PileCacheKey) Redis-->>Repo : 缓存命中/未命中 alt Redis命中 Repo->>Caffeine : put(数据) Repo-->>Client : 返回数据 else Redis未命中 Repo->>DB : selectByCode(pileCode) DB-->>Repo : 返回实体 Repo->>Redis : put(实体, version) Repo->>Caffeine : put(实体) Repo-->>Client : 返回实体 end end Note over Client,Repo : 读取流程:Caffeine → Redis → DB Client->>Repo : updatePile(pile) Repo->>DB : update(pile) DB-->>Repo : 更新成功 Repo->>Repo : 发布PileCacheEvictEvent Repo->>Caffeine : evict(相关缓存键) Repo->>Redis : evict(相关缓存键) Repo-->>Client : 操作完成 Note over Client,Repo : 写入流程:更新DB → 删除两级缓存 ``` **图示来源** - [PileRepositoryImpl.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java#L35-L45) - [VersionedCaffeineCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCaffeineCache.java#L30-L50) - [VersionedRedisCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java#L80-L100) **本节来源** - [PileRepositoryImpl.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java) - [AbstractCachedEntityRepository.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractCachedEntityRepository.java) ### 读取流程 缓存的读取遵循"先本地,后远程"的原则: 1. 首先尝试从Caffeine本地缓存中获取数据 2. 如果本地缓存未命中,则查询Redis分布式缓存 3. 如果Redis缓存也未命中,则访问数据库获取数据 4. 将数据库查询结果依次写入Redis和Caffeine缓存,供后续请求使用 这种分层查询策略最大限度地利用了本地缓存的高性能优势,同时保证了数据的最终一致性。 ### 写入流程 写入操作采用"先数据库,后缓存"的策略: 1. 首先更新数据库中的数据 2. 发布缓存失效事件(如`PileCacheEvictEvent`) 3. 在事件处理中删除两级缓存中的相关数据 这种"写后失效"(Write-Through with Cache-Aside)模式确保了数据的一致性,避免了缓存与数据库之间的数据偏差。 ## 缓存键设计 ```mermaid classDiagram class PileCacheKey { -UUID pileId -String pileCode +PileCacheKey(UUID pileId) +PileCacheKey(String pileCode) +toString() +isVersioned() } class VersionedCacheKey { +isVersioned() } PileCacheKey --> VersionedCacheKey : 实现 note right of PileCacheKey 缓存键设计原则: 1. 支持多种查询方式 2. 版本控制标识 3. 序列化兼容性 end ``` **图示来源** - [PileCacheKey.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java) **本节来源** - [PileCacheKey.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java) ### PileCacheKey设计原则 `PileCacheKey`的设计体现了以下核心原则: 1. **多维度查询支持**:同时支持通过充电桩ID(`pileId`)和桩编码(`pileCode`)两种方式创建缓存键,满足不同场景的查询需求。 2. **版本控制标识**:通过`isVersioned()`方法区分是否需要版本控制,当通过`pileId`查询时不需要版本控制,而通过`pileCode` 查询时需要版本控制。 3. **简洁的toString实现**:`toString()`方法优先返回`pileId`,不存在时返回`pileCode`,确保缓存键的可读性和一致性。 4. **序列化兼容**:使用`@Serial`注解和`serialVersionUID`确保序列化兼容性,避免版本升级导致的问题。 这种设计既满足了功能需求,又考虑了性能和可维护性。 ## 缓存失效策略 ```mermaid flowchart TD Start([缓存失效触发]) --> ActiveEviction["主动失效策略"] Start --> PassiveEviction["被动失效策略"] ActiveEviction --> EventDriven["事件驱动失效"] EventDriven --> PileEvictEvent["PileCacheEvictEvent"] PileEvictEvent --> HandleEvict["handleEvictEvent处理"] HandleEvict --> EvictCaffeine["删除Caffeine缓存"] HandleEvict --> EvictRedis["删除Redis缓存"] PassiveEviction --> TTL["TTL过期"] TTL --> CaffeineTTL["Caffeine TTL配置"] CaffeineTTL --> expireAfterWrite["expireAfterWrite"] TTL --> RedisTTL["Redis TTL配置"] RedisTTL --> cacheTtl["cacheTtl参数"] EvictCaffeine --> End([缓存失效完成]) EvictRedis --> End expireAfterWrite --> End cacheTtl --> End style ActiveEviction fill:#f96,stroke:#333 style PassiveEviction fill:#69f,stroke:#333 ``` **图示来源** - [PileCacheEvictEvent.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheEvictEvent.java) - [PileRepositoryImpl.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java#L40-L48) - [JCPPCaffeineCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPCaffeineCacheConfiguration.java#L60-L70) - [VersionedRedisCache.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java#L50-L60) **本节来源** - [PileCacheEvictEvent.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheEvictEvent.java) - [PileRepositoryImpl.java](file://jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java#L40-L48) ### 主动失效 主动失效通过事件驱动机制实现: - 当充电桩数据被修改或删除时,发布`PileCacheEvictEvent`事件 - `PileRepositoryImpl`中的`@TransactionalEventListener`监听并处理该事件 - 在事件处理方法`handleEvictEvent`中,删除与该充电桩相关的所有缓存键 这种设计确保了数据变更后缓存能够及时失效,避免了脏数据的传播。 ### 被动失效 被动失效依赖于TTL(Time To Live)机制: - Caffeine缓存通过`expireAfterWrite`配置写后过期时间 - Redis缓存通过`cacheTtl`参数配置过期时间 - 当缓存项超过设定的生存时间后自动失效 被动失效作为主动失效的补充,提供了额外的安全保障,防止因事件机制故障导致的缓存数据长期不一致。 ## 性能监控与优化 ```mermaid graph LR Metrics[性能监控指标] --> HitRate["缓存命中率"] Metrics --> Latency["访问延迟"] Metrics --> Memory["内存使用"] Metrics --> QPS["查询吞吐量"] HitRate --> CaffeineHit["Caffeine命中率"] HitRate --> RedisHit["Redis命中率"] Latency --> LocalLatency["本地缓存延迟"] Latency --> RemoteLatency["远程缓存延迟"] Memory --> CaffeineMem["Caffeine内存"] Memory --> RedisMem["Redis内存"] QPS --> ReadQPS["读取QPS"] QPS --> WriteQPS["写入QPS"] Optimization[优化建议] --> SizeTuning["容量调优"] Optimization --> TTLTuning["TTL调优"] Optimization --> PatternAnalysis["访问模式分析"] Optimization --> Monitoring["监控告警"] SizeTuning --> MaxSize["设置合理的maxSize"] TTLTuning --> OptimalTTL["根据业务需求设置TTL"] PatternAnalysis --> Hotspot["识别热点数据"] Monitoring --> Alert["设置命中率告警"] style HitRate fill:#ff6,stroke:#333 style Optimization fill:#6f6,stroke:#333 ``` **图示来源** - [JCPPCaffeineCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPCaffeineCacheConfiguration.java#L60-L70) - [CacheSpecs.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CacheSpecs.java) **本节来源** - [JCPPCaffeineCacheConfiguration.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/JCPPCaffeineCacheConfiguration.java) - [CacheSpecsMap.java](file://jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CacheSpecsMap.java) ### 性能监控指标 建议监控以下关键性能指标: 1. **缓存命中率**:衡量缓存有效性的核心指标,应分别监控Caffeine和Redis的命中率。 2. **访问延迟**:记录从各级缓存获取数据的响应时间,识别性能瓶颈。 3. **内存使用**:监控Caffeine和Redis的内存占用情况,防止内存溢出。 4. **查询吞吐量**:统计单位时间内的缓存读写操作数量。 ### 优化建议 1. **容量调优**:根据实际内存资源和访问模式,合理设置Caffeine的`maxSize`和Redis的内存限制。 2. **TTL调优**:根据业务数据的变更频率,设置合适的TTL值,平衡数据新鲜度和缓存效率。 3. **访问模式分析**:通过监控识别热点数据,考虑对热点数据进行特殊处理或预加载。 4. **监控告警**:设置缓存命中率的告警阈值,当命中率异常下降时及时排查问题。 ## 结论 JChargePointProtocol的多级缓存架构通过Caffeine和Redis的有机结合,在性能和数据一致性之间取得了良好平衡。 `CachedVersionedEntityRepository` 通过版本号机制有效解决了缓存一致性问题,避免了脏读现象。缓存键的精心设计支持了多种查询场景,而主动与被动相结合的失效策略确保了数据的及时更新。通过合理的性能监控和持续优化,该缓存架构能够有效支撑系统的高并发访问需求。