问题/危害

  • 当前 Key 为热点 key(如:热门的娱乐资讯),并发量非常大
  • 重建缓存不能有效即时完成,如:一个复杂的运算,查询等;当缓存失效,大量请求来重建缓存,造成后端负载大

优化目标

  • 减少重建缓存的次数
  • 数据尽可能一致
  • 较少的潜在危险

解决方案

  • 互斥锁(mutex key)

只允许一个线程重建缓存,其他线程等待。如下为 Java 方式实现互斥锁思路:

  1. String get(String key) {
  2. // 从Redis中获取数据
  3. String value = redis.get(key);
  4. // 如果value为空,则开始重构缓存
  5. if (value == null) {
  6. // 只允许一个线程重构缓存,使用nx,并设置过期时间ex
  7. String mutexKey = "mutext:key:" + key;
  8. if (redis.set(mutexKey, "1", "ex 180", "nx")) {
  9. // 从数据源获取数据
  10. value = db.get(key);
  11. // 回写Redis,并设置过期时间
  12. redis.setex(key, timeout, value);
  13. // 删除key_mutex redis.delete(mutexKey);
  14. }
  15. // 其他线程休息50毫秒后重试
  16. else {
  17. Thread.sleep(50); get(key);
  18. }
  19. }
  20. return value;
  21. }
  • 数据永不过期,两层意思:
    • 从缓存层面看,不设置过期时间
    • 从功能层面看,设置过期时间,当发现超过过期时间,会使用单独线程进行重建缓存

问题:可能存在缓存数据与数据库数据不一致的情况,看是否容忍。

  • 热点 key 进行拆分多 key

基于如 Redis Cluster 的哈希槽概念,拆分多 key ,尽可能的让更多的节点承担缓存层的流量负载。

对比

使用缓存时有三个目标:

  • 加快用户访问速度,提高用户体验
  • 降低后端负载,减少潜在风险,保证系统稳健
  • 保证数据“尽可能”的及时更新
解决方案 优点 缺点
互斥锁(分布式锁)
- 思路简单
- 保证一致性

- 代码复杂度大
- 存在死锁风险
- 存在线程阻塞的风险
“永不过期”
- 基本杜绝热点 key 情况

- 不保证一致性
- 逻辑过期时间增加代码维护成本和内存成本
key 拆分
- 提升热点 key 性能

- 代码复杂度大
- 增加多 key 维护成本