Java微服务项目,结合Caffeine和Redis实现热点数据缓存方案,方案如下:用户查询商品时,先查redis(1小时过期),没有就查caffeine(1分钟过期,且开启了recordStats()统计功能),没有就查MySQL,然后使用RocketMQ发布普通消息广播通知所有当前服务的集群,把商品信息存到caffeine中,caffeine记录这个商品的访问次数加1。然后又来一次查询这个商品的请求,先查redis,没有就查caffeine,这时查到了,依然是RocketMQ发布普通消息。循环往复,直到又来了一次请求,到caffeine这里发现已经记录了9次,说明这个商品在该实例中1分钟内被访问了10次,直接将这个商品信息存到Redis中。如此,便实现了结合Caffeine和Redis实现热点数据缓存的方案。
下面是一个序列图,直观展示热点数据缓存方案的工作流程:
sequenceDiagram
participant 用户
participant 服务实例A
participant Redis
participant Caffeine
participant MySQL
participant RocketMQ
participant 服务实例B
Note over 用户,服务实例B: 场景1:首次查询商品(冷数据)
用户->>服务实例A: 查询商品ID=1001
服务实例A->>Redis: GET product:1001
Redis-->>服务实例A: 返回null
服务实例A->>Caffeine: GET product:1001
Caffeine-->>服务实例A: 返回null
服务实例A->>MySQL: SELECT * FROM products WHERE id=1001
MySQL-->>服务实例A: 返回商品数据
服务实例A->>RocketMQ: 发送ADD消息(商品数据)
RocketMQ->>服务实例A: 广播消息
RocketMQ->>服务实例B: 广播消息
服务实例A->>Caffeine: PUT(商品数据, count=1)
服务实例B->>Caffeine: PUT(商品数据, count=1)
服务实例A-->>用户: 返回商品数据
Note over 用户,服务实例B: 场景2:第2~9次查询(温数据)
用户->>服务实例B: 查询商品ID=1001
服务实例B->>Redis: GET product:1001
Redis-->>服务实例B: 返回null
服务实例B->>Caffeine: GET product:1001
Caffeine-->>服务实例B: 返回商品(count=1)
服务实例B->>RocketMQ: 发送INCREMENT消息(商品ID)
RocketMQ->>服务实例A: 广播消息
RocketMQ->>服务实例B: 广播消息
服务实例A->>Caffeine: count+1 (count=2)
服务实例B->>Caffeine: count+1 (count=2)
服务实例B-->>用户: 返回商品数据
Note over 用户,服务实例B: 场景3:第10次查询(升级热数据)
用户->>服务实例A: 查询商品ID=1001
服务实例A->>Redis: GET product:1001
Redis-->>服务实例A: 返回null
服务实例A->>Caffeine: GET product:1001
Caffeine-->>服务实例A: 返回商品(count=9)
服务实例A->>Redis: SETEX product:1001 (1小时)
服务实例A-->>用户: 返回商品数据
Note over 用户,服务实例B: 场景4:后续查询(热数据)
用户->>服务实例A: 查询商品ID=1001
服务实例A->>Redis: GET product:1001
Redis-->>服务实例A: 返回商品数据
服务实例A-->>用户: 返回商品数据
图例说明:
1. 冷数据查询流程(首次查询):
- 查询Redis → 未命中
- 查询Caffeine → 未命中
- 查询MySQL → 命中
- 广播ADD消息 → 所有实例存入Caffeine(初始计数=1)
2. 温数据查询流程(第2~9次查询):
- 查询Redis → 未命中
- 查询Caffeine → 命中
- 广播INCREMENT消息 → 所有实例增加计数
3. 热数据升级流程(第10次查询):
- 查询Redis → 未命中
- 查询Caffeine → 命中(计数=9)
- 立即将数据存入Redis(1小时过期)
4. 热数据查询流程(第11次及后续查询):
- 直接命中Redis缓存
- 不访问Caffeine和MySQL