热点数据过期时间设计:平衡一致性与性能

热点数据过期时间设计:平衡一致性与性能

问题的本质

过期时间(TTL)是缓存系统最核心的参数之一。设得太短,缓存命中率低,数据库压力大;设得太长,数据可能过时,用户看到脏数据。对于热点数据,这个问题更加突出——击穿风险、数据一致性、存储成本交织在一起。

过期时间设计的核心矛盾

设定方向 优势 劣势
TTL 过长 高命中率、低 DB 压力 数据可能不一致、占用内存
TTL 过短 数据新鲜、节省内存 低命中率、高 DB 压力、雪崩风险

设计策略

1. 根据数据变化频率设计

不同类型的热点数据有不同的变化频率,应设置不同的 TTL:

// 几乎不变的数据:配置信息、词典
CONFIG_TTL = 24 * 60 * 60;  // 24小时

// 低频变化:用户基本信息、商品描述
USER_BASIC_TTL = 60 * 60;   // 1小时

// 中频变化:商品销量、库存
PRODUCT_STATS_TTL = 5 * 60; // 5分钟

// 高频变化:实时热度、排行榜
REALTIME_TTL = 10;           // 10秒

2. 随机化过期时间

同一类热点数据的 TTL 加上随机偏移,避免大面积同时过期:

// 基础 TTL 30分钟,随机偏移 ±5 分钟
int baseTtl = 1800;
int jitter = new Random().nextInt(600);
int ttl = baseTtl + jitter;
redis.set(key, value, ttl, TimeUnit.SECONDS);

// 或者更均匀的分布:±30%
int ttl = (int)(baseTtl * (0.7 + Math.random() * 0.6));

效果:原 TTL 30 分钟,1000 个 key 会在同一秒过期。随机化后,过期时间均匀分布在 21~39 分钟内,大大降低同时穿透的概率。

3. 分层 TTL 设计

将数据按照热度分层,不同层级使用不同的 TTL:

一级缓存(本地内存 Caffeine):TTL = 5 秒,存储最热的 1000 条
二级缓存(Redis):TTL = 30 分钟,存储全量热点
三级存储(MySQL):作为最终数据源

越靠近用户的层 TTL 越短,保证数据新鲜;越靠近存储的层 TTL 越长,保护数据库。

4. 动态 TTL

根据实际访问频率动态调整 TTL:

public class DynamicTtl {
    private final Map<String, AtomicInteger> accessCount = new ConcurrentHashMap<>();

    public void recordAccess(String key) {
        accessCount.computeIfAbsent(key, k -> new AtomicInteger()).incrementAndGet();
    }

    public int calculateTtl(String key) {
        int count = accessCount.getOrDefault(key, new AtomicInteger()).get();
        if (count > 1000)  return 3600;  // 超热点 1小时
        if (count > 100)   return 600;   // 热点 10分钟
        return 60;                        // 普通 1分钟
    }
}

5. 逻辑过期(软过期)

设置一个比实际 TTL 更短的逻辑过期时间,过期后返回旧数据并触发异步刷新:

public class CacheItem<T> {
    private T data;
    private long createTime;

    // 物理 TTL 设为 1 小时,逻辑 TTL 设为 50 分钟
    public boolean isLogicallyExpired() {
        return System.currentTimeMillis() - createTime > 50 * 60 * 1000;
    }
}

public T getSmart(String key) {
    CacheItem<T> item = redis.get(key);
    if (item == null) return loadFresh(key);  // 穿透了
    if (item.isLogicallyExpired()) {
        threadPool.submit(() -> loadFresh(key));  // 异步刷新
    }
    return item.data;  // 先返回旧数据
}

不同业务场景的最佳实践

业务场景 TTL 策略 理由
商品详情 10~30 分钟 + 随机偏移 时效性要求不高,命中率优先
库存数量 5~10 秒 实时性要求高,使用短 TTL
用户 Session 30 分钟 + 续期 每次访问重新刷新 TTL
配置字典 1~24 小时 几乎不变,可手动刷新
排行榜 30 秒 ~ 1 分钟 不需要实时精准,允许短暂延迟

面试要点

  • 过期时间设计没有通用答案,取决于数据一致性要求和系统负载
  • 随机化过期时间是性价比最高的防雪崩手段
  • 动态 TTL 和逻辑过期是高阶方案,体现对问题的深入理解
  • 能结合多级缓存设计分层 TTL 是加分项
  • 面试时可以给出的黄金建议:“先做随机化 TTL,再配合逻辑过期,最后考虑动态调整”
© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容