1.热点数据处理。

场景:流量明星发送微博信息,一分钟内涌入5000万人。

首先我们要对数据的流动进行充分的理解,用户点击微博,点开热点信息,点赞,评论,转发,客户端传入数据库,进行缓存,缓存拦截大量重复。结合微服务,就还要加一个网关。

考察:

1.能不能对并发流量问题拆解多个层次。

2.知不知道对核心业务的取舍

3.,理解高并发的过程

分析:热点流量特点是来的快去的也快,大量的读写操作,并发。核心业务,点赞,评论。

解决组合:

所有操作都要围绕,不要让数据库崩掉,让少数的数据进入数据库,大量的数据被拦截,围绕这个核心可以得出。

拆分四个层次。

第一层,多级缓存抗流量。事件特点是读多写少

1.redis缓存进行抗读,大量的数据需要在redis层被拦截。

最外层,cdn静态资源,比如图片视频,推到边缘节点,用户刷微博,用不上服务器。

第二层,本地缓存,将热点数据放在内存里面,连redis都不用差。

第三层redis 保证高可用(这是又涉及redis的穿透,雪崩,击穿,持久化,)

【redis】缓存穿透 缓存击穿 缓存雪崩-CSDN博客 链接失效去主页找。

第二层,当流量太大缓存也抗不住,就要进行服务降级操作。保证核心业务,边缘业务先放一放。

如刷微博,写微博,评论是核心业务,个性推荐,是边缘业务。

读可以降级为过期数据,点赞延迟更新,评论去显示部分评论,

写可以异步写入,使用消息队列异步处理。

第三层限流保护

两个核心算法,令牌桶算法,漏桶算法。

为什么限流?一个核心,保证系统不被大量数据冲爆。假设有五万张票,但是你不能让50万人抢,所以得限流,让他们排队。

限流可以分层,如果有网关层,网关层可以进行分流,单台服务器每秒最多10万请求,超出返回服务层繁忙,应用层利用算法,对每个接口进行单独限流,比如发微博接口每秒一万,点赞每秒五万,数据采用连接池限制,最多启用1000个连接,保护数据库安全。

第四层,增加硬实力,动态扩容。

,服务器进行扩容,但是要区分情况,如热点数据来的快去的也快,进行扩容就太浪费资源了。

使用云原能力,kubernetes自动伸缩,Docker镜像。

但是记住,阔再多,数据库扛不住,在多也没有,日活高,像微信,qq等数据库一定是分库分表的,按用户id哈希,将数据打散到库里面。

最后还可以加消息队列削峰,争对大量猛增的数据,一会突然下降的数据,使用kafka削峰高吞吐,用户发微博,点赞评论,仍进队列,后端慢慢处理。

总结

缓存扛读,降级保护核心,限流防打死数据库,扩容补资源,队列削峰填谷,

2.发红包场景

多层防御架构(从外到内,层层兜底)

第一层:缓存层核心防护(第一道主力防线)

1. 分布式互斥锁:串行重建缓存,禁止并发查库

核心思路:缓存失效时,只允许一个线程去 DB 查数据并重建缓存,其余线程等待 / 重试。

  • 落地实现:Redisson分布式锁(Redis 官方推荐,自带锁续期、防死锁)
  • 优化:自旋重试 + 超时控制,避免大量线程阻塞占用连接
  • 适用场景:强一致性要求、热点 Key 更新频率低的场景(如商品详情)

2. 逻辑过期:永不主动删 Key,后台异步刷新

核心思路:缓存 Value 里存过期时间戳,业务线程只判断时间戳是否过期,不阻塞;过期后立即返回旧值,同时用异步线程去 DB 拉新数据更新缓存。

  • 优点:无锁、无阻塞、吞吐量拉满,完全不打崩 DB
  • 缺点:短时间会读到旧数据(最终一致性)
  • 适用场景:高并发秒杀、热搜、榜单等允许短暂脏读的场景

3. 热点 Key 永久不过期 + 主动异步刷新

核心思路:

  • 标记热点 Key,设置TTL=-1永不过期
  • 后台定时任务 / 消息队列异步更新缓存
  • 配合缓存预热,服务启动时提前加载热点数据
  • 适用场景:超级热点 Key(如爆款商品、首页 Banner)

4. 多级缓存:本地缓存兜底,避免穿透到 Redis

架构:JVM本地缓存(Caffeine) + Redis分布式缓存

  • 热点 Key 先放 Caffeine(本地内存,O (1) 访问,比 Redis 快 10~100 倍)
  • 本地缓存过期后再查 Redis,Redis 过期再走锁重建
  • 关键:本地缓存设置短 TTL,避免数据不一致 + 内存溢出
  • 优势:即使 Redis 宕机 / 抖动,本地缓存仍能扛住流量

第二层:应用层流量管控(第二道防线,削峰填谷)

1. 热点 Key 自动探测与标记

  • 实时统计 QPS、访问频次,自动识别热点 Key
  • 对识别出的热点 Key:提前预热、永久不过期、放入本地缓存
  • 落地:Redis 客户端热 Key 统计、网关层埋点、日志分析

2. 接口限流 + 熔断降级

  • 网关 / 服务层对热点接口做单机限流 + 分布式限流
  • 超出阈值直接熔断,返回兜底数据(静态页、默认值)
  • 配合 Sentinel/Hystrix,自动降级,防止流量压垮服务

3. 请求合并 / 批处理

高并发下,将同一 Key 的并发请求合并,只执行一次 DB 查询,结果复用给所有请求

  • 落地:Guava Cache、自定义合并器、消息队列攒批

第三层:数据库层抗压(第三道防线,最后兜底)

1. 数据库限流 & 读写分离

  • 读请求走从库,主库只负责写入,避免主库被打挂
  • DB 层配置连接池限流、SQL 限流,禁止无限制查询

2. 索引优化 + 轻量查询

  • 热点查询必须命中主键 / 唯一索引,禁止慢查询
  • 只查必要字段,减少 DB IO 与 CPU 开销
  • 禁止事务 + 长查询,避免锁等待

3. 分库分表 / 冷热数据分离

热点数据单独分片,避免与冷数据争抢资源,提升查询效率


第四层:架构兜底与一致性保障(终极防线)

1. 静态兜底数据

  • 预生成静态页面 / 默认 JSON,缓存击穿时直接返回兜底数据
  • 对用户无感知,保证服务可用

2. 缓存 + DB 双写一致性

避免重建缓存时出现数据不一致:

  • 方案:先更DB,再删缓存(Cache Aside Pattern)
  • 异步更新:通过 Canal 订阅 MySQL binlog,自动同步 Redis

第五层:监控预警 & 主动治理(防患于未然)

  1. 监控指标
    • Redis 热 Key 数量、命中率、过期 Key 分布
    • DB 连接数、慢查询、CPU 负载
    • 接口 QPS、熔断次数、降级比例
  2. 自动预警
    • 缓存命中率低于阈值告警
    • 热点 Key 即将过期自动提醒
  3. 主动扩容
    • 热点时段自动扩容 Redis / 应用节点
    • 临时提升 DB 读能力

资深程序员生产级「最佳组合拳」

根据业务一致性要求,分两套落地方案:

方案 A:强一致性(商品详情、订单)

多级缓存(Caffeine+Redis) + Redisson分布式锁 + DB读写分离 + 限流熔断

方案 B:高并发最终一致性(秒杀、热搜、榜单)

本地缓存永久热点Key + 逻辑过期 + 异步刷新 + 熔断降级 + 静态兜底


必避坑点(资深踩坑总结)

  1. 不要用本地锁(synchronized/Lock) 应对集群部署,会出现多节点同时查库
  2. 互斥锁必须加超时时间,防止线程崩溃导致死锁
  3. 逻辑过期不要和强一致性业务混用,避免客诉
  4. 热点 Key 不要设置随机 TTL 打散,会人为制造击穿
  5. 禁止缓存重建时做复杂业务计算,拖慢响应、占用线程

一、整体流程概览

核心思想:缓存未命中 → 抢分布式锁 → 只允许一个线程查库更新缓存 → 其他线程等待重试 → 缓存更新后全部命中返回

关键设计点

  1. 双检缓存:抢锁前后都查一次缓存,避免重复查库
  2. 锁自动续期:防止查询 DB 过慢导致锁提前释放
  3. 有限重试:避免线程无限自旋占用资源
  4. 空值缓存:顺便解决缓存穿透
  5. finally 释放锁:绝对防止死锁

二、详细执行步骤(逐行可落地)

假设有一个热点 Key:product:detail:1001

步骤 1:接收请求,先查 Redis 缓存

plaintext

请求 → get(product:detail:1001)
  • 命中:直接返回数据,流程结束
  • 未命中:进入锁逻辑

步骤 2:【第一次双检】再次查缓存

为什么?高并发下,可能刚有其他线程更新完缓存,你没必要再抢锁。

plaintext

if (cache.exist(key)) {
    return cache.get(key);
}

步骤 3:尝试获取分布式锁

锁 Key 建议:lock:cache:product:detail:1001

  • 使用 Redisson RLock lock = redisson.getLock(lockKey);
  • 加锁:lock.lock(5, TimeUnit.SECONDS);
    • 5s 是锁超时时间,Redisson 会自动续期(watchdog 机制)

步骤 4:【第二次双检】再次查缓存

这一步极其重要!可能你在等待锁的过程中,其他线程已经把缓存重建好了。

plaintext

if (cache.exist(key)) {
    return cache.get(key);
}

步骤 5:只有抢到锁 & 缓存仍不存在 → 查询数据库

plaintext

Data data = dbMapper.selectById(id);

步骤 6:将数据写回 Redis

  • 数据非空:写入缓存 + 设置正常 TTL(如 30min)
  • 数据为空:写入空值 / 默认值 + 设置短 TTL(如 1min),防穿透

plaintext

redis.set(key, data, 30, TimeUnit.MINUTES);

步骤 7:释放分布式锁

必须放在 finally 里,保证任何异常都能释放

plaintext

finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

步骤 8:返回数据

未抢到锁的线程处理逻辑:

  1. 线程 sleep 一小段时间(如 50ms)
  2. 重试查缓存(最多 3~5 次)
  3. 重试成功:返回缓存数据
  4. 重试失败:返回兜底数据 / 抛出限流异常

三、标准伪代码(Java 风格,可直接改写成业务代码)

java

运行

public ProductDetail getProductDetail(Long productId) {
    String cacheKey = "product:detail:" + productId;
    String lockKey = "lock:cache:" + cacheKey;

    // 1. 查缓存
    ProductDetail cache = redisTemplate.get(cacheKey);
    if (cache != null) {
        return cache;
    }

    RLock lock = redissonClient.getLock(lockKey);
    try {
        // 尝试加锁,最多等待 1s,锁持有 5s(Redisson自动续期)
        boolean lockSuccess = lock.tryLock(1, 5, TimeUnit.SECONDS);
        if (!lockSuccess) {
            // 未抢到锁:短暂休眠重试
            Thread.sleep(50);
            // 最多重试2次
            for (int i = 0; i < 2; i++) {
                cache = redisTemplate.get(cacheKey);
                if (cache != null) return cache;
                Thread.sleep(50);
            }
            // 重试失败:返回兜底或抛异常
            return getDefaultDetail(productId);
        }

        // 2. 抢到锁后【再次双检】
        cache = redisTemplate.get(cacheKey);
        if (cache != null) {
            return cache;
        }

        // 3. 只有真正需要,才查DB
        ProductDetail dbData = productMapper.selectById(productId);

        // 4. 写回缓存(空值处理)
        if (dbData != null) {
            redisTemplate.set(cacheKey, dbData, 30, TimeUnit.MINUTES);
        } else {
            // 防穿透:缓存空值,短过期
            redisTemplate.set(cacheKey, EMPTY_DETAIL, 1, TimeUnit.MINUTES);
        }

        return dbData;

    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return getDefaultDetail(productId);
    } finally {
        // 必须释放锁
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

四、为什么必须这么设计?(资深要点)

1. 为什么要两次查缓存?

  • 第一次:过滤掉大部分并发
  • 第二次:防止你等待锁期间,别人已经重建好缓存避免:1000 个并发同时抢锁,都去查 DB

2. 为什么用 Redisson 而不是手写 SETNX?

  • Redisson 自带 watchdog 锁续期:查询 DB 慢也不会锁超时释放
  • 可重入、防误删、防死锁
  • 手写 SETNX + DEL 极易在异常时造成死锁

3. 为什么未抢到锁要重试而不是直接报错?

热点 Key 重建极快(毫秒级),重试 2~3 次基本都能命中缓存,用户无感知。

4. 锁超时设多久合适?

  • 锁等待时间:1s 以内
  • 锁持有时间:5s(Redisson 会自动续期)
  • 避免锁时间太长,导致故障时阻塞太久

五、极端异常场景兜底

  1. Redis 挂了→ 关闭缓存逻辑,直接限流 + 降级走 DB
  2. DB 查询超时→ 锁自动释放,下一个线程重试
  3. 应用突然宕机→ Redisson 锁超时自动释放,无死锁
  4. 大量线程同时重试→ 配合接口限流,防止雪崩

六、总结最简流程口诀

先查缓存,未命中抢锁抢到再查,避免重复查库查库写缓存,空值也要存finally 释放锁,重试不死锁

更多推荐