热点 key 重建优化

定义

  • 在缓存没命中的情况下,从数据库中查数据,到在缓存中更新,有一个时间窗口;
  • 如果数据库的查询时间很长,并且在这个窗口期有大量的缓存 miss 的情况,就会在窗口期发生大量的数据库查询操作;
  • 这样对数据库造成很大的压力;

解决方案的三个目标

  • 减少重建缓存的次数;
  • 数据尽可能一致;
  • 减少潜在风险:死锁,线程池被大量 hang 住;

解决方案(一)互斥锁

  • 第一个重建 key 的请求在缓存把 key 锁住,缓存重建完了再打开;
  • 后续对该 key 重建的请求等待第一个重建请求完成,直接缓存命中;
优点
  • 思路简单,保证一致性;
缺点
  • 存在大量线程等待,被 hang 住的问题;
示例伪代码
String get(String key) {
    String value = redis.get(key);
    if(value == null) {
        String mutexKey = "mutex:key:" + key;
        if(redis.set(mutexKey, "1", "ex 180", "nx")) {
            value = db.get(key);
            redis.set(key, value);
            redis.delete(mutexKey);
        } else {
            Thread.sleep(50);
            get(key);
        }
    }
    return value;
}

解决方案(二)永不过期

  • 缓存层面:没有为 key 设置过期时间;
  • 功能层面:为每个 key 添加逻辑过期时间,只是发现其超过逻辑过期时间后,会使用单独的线程去重构缓存;
优点
  • 基本杜绝热点 key 重建问题;
缺点
  • 会存在数据不一致问题,即重建线程没重建完,另一个线程拿到了老数据;
示例伪代码
String get(final String key) {
    V v = redis.get(key);
    String value = v.getValue();
    long logicTimeout = v.getLogicTimeout();
    if(logicTimeout >= System.currentTimeMills()) {
        String mutexKey = "mutex:key:" + key;
        if(redis.set(mutexKey, "1", "ex 180", "nx")) {
            threadPool.execute(() -> {
                String dbValue = db.get(key);
                redis.set(key, (dbValue, newLogicTimeout));
                redis.delete(keyMutex);
            });
        }
    }
    return value;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值