← 大纲 第一部分 · 高并发系统架构 · 第 1~15 题

第一部分:高并发系统架构(第 1~15 题)

本批 10 题均属「第一阶段:基础架构能力」,但要求给出架构级回答:明确数据规模 → 性能指标 → 组件职责 → 瓶颈与权衡 → 故障兜底。

本页目录: #1 秒杀#2 商品详情多级缓存#3 限流体系 #4 优惠券防刷#5 热点库存分片#6 异步下单 #7 全链路压测#8 实时计数排行#9 库存中心对账 #10 性能与成本#11 库存预热回源#12 下单排队 #13 大促隔离#14 秒杀风控#15 库存预热与防刷

第 1 题:电商秒杀——50 万 QPS 抢购与库存防超卖 高并发

1.【真实企业业务场景】
某头部电商平台:注册用户 5000 万、DAU 1000 万、日常峰值 QPS 20 万、SKU 1000 万、订单库 MySQL 分库分表(16 库×64 表)写入上限约 2 万 TPS。现做「iPhone 新品 1 元秒杀」:单 SKU 库存 1 万件、限购 1 件/人、预约 500 万人、活动瞬间峰值 QPS 50 万。Redis 集群(8 分片)读 60 万 / 写 20 万 QPS,DB 整体承压 ≤ 2 万 QPS。SLA:下单 P99 < 500ms、超卖 = 0、订单 5s 内最终可见。
2.【面试官问题】
    1. 整体架构如何设计?2. 如何防止库存超卖?3. Redis 与 DB 库存如何一致?4. Redis 宕机怎么办?5. 如何削峰让 2 万 TPS 的库承接 50 万 QPS?6. 如何避免请求直接打库?7. 如何保证订单/库存/积分最终一致?8. QPS 从 50 万涨到 100 万,哪先崩、怎么扩?
3.【候选人的标准回答】

第一步:分析问题。秒杀本质不是「高并发写」,而是读多写少 + 极小有效请求比例。1 万库存面对 500 万用户,99.8% 请求注定失败。第一性原理:尽早、廉价地拒绝无效请求,把 50 万 QPS 收敛到 1 万次成功写入。

第二步:核心挑战。超卖=0 件(资损红线);DB 承压 ≤1.5 万 QPS;单 Key 热点;重复下单;最终一致延迟 ≤5s;脚本拦截率 >99%。

第三步:整体架构。「四层拦截 + 异步下单 + 数据库终判」:①客户端答题/随机延迟削 30%;②CDN+WAF+网关限流到 5 万;③Redis Lua 原子预扣(库存/一人一单/售罄一次完成)放行约 1.2 万;④RocketMQ 串行消费匀速落库。核心:Redis 决定谁能买,DB 决定买成不成,Lua 保证不超卖

第四步:技术选型。Redis Cluster(8 分片)+ 库存分段 + Lua;RocketMQ(事务/顺序/重试/死信);MySQL 条件扣减为最终裁判;Gateway+Redis 用户维度限流;雪花算法订单号。

第五步:一致性。预扣 → MQ → DB 条件扣减 → 对账回补。允许少卖、绝不允许超卖。

第六步:高可用。Redis 主从自动转移;全挂降级 DB 乐观锁直扣(限 2000 QPS);秒杀集群与交易主链路物理隔离(独立连接池/线程池)。

第七步:性能优化。库存分段打散热 Key;缓存预热;Caffeine 本地售罄标记(零 Redis 调用);singleflight 合并查询。

4.【架构设计】
用户 App/H5 │ 静态资源 / 活动页 / 商品图文(CDN 边缘拦截 90% 流量) ▼ CDN / 边缘节点 ▼ WAF / 风控(设备指纹、黑名单、Bot 识别) ▼ API Gateway(限流第一层:全局令牌桶放行 5 万 QPS;用户维度 5s/1 次) ▼ 秒杀服务(200 副本,独立集群) ├ 本地缓存:活动配置、售罄标记(Caffeine 1s TTL) └ Redis Lua:原子预扣库存 + 一人一单 + 售罄判断 ▼ Redis Cluster(8 分片,库存分段存储) ▼ 成功 → 发送下单消息 RocketMQ(SECKILL_ORDER_TOPIC,顺序消息,单分区 3000 TPS) ▼ 订单服务(幂等落库 + DB 条件扣减) ▼ MySQL(16 库×64 表) ├ Binlog → Canal → Kafka → 数据仓库(对账/大屏/风控) └ 对账任务(30s):Redis 库存 vs DB 库存差异告警

潜在瓶颈:网关热点、Redis 单分片热 Key(分段解决)、MQ 消费能力不足积压、DB 行锁竞争(分段+合并扣减)。

5.【技术方案深度解析】

为什么用 Redis 而非直写库?1 万库存只有 1 万次有效写入,但入口 500 万请求;直落库会让 499 万无效请求打满连接池与行锁,连累有效请求。Redis 十万级 QPS + Lua 原子性天然做「阀门」。

为什么用 Lua 而非分布式锁?分布式锁需「加锁→读→扣→解锁」四次往返,还要处理超时/误释放/GC 致锁失效;Lua 在 Redis 内一次性原子执行,一次 RTT 完成判断+扣减,无锁无死锁,性能高一数量级。

为什么不能直接写库防超卖?SELECT stock → UPDATE 存在读写竞态,并发必超卖。必须 UPDATE ... SET stock=stock-1 WHERE sku_id=? AND stock>0,靠受影响行数判定成败。

Redis 与 MySQL 一致性?预扣+终判+对账三段式:Redis 逻辑库存(快)、DB 物理库存(权威);DB 扣失败/消息丢失 → 回补 Redis 库存(宁可少卖不超卖);30s 对账比对差异并纠偏。

Redis 宕机?①分片故障:Cluster 秒级主从切换+客户端重试;②集群整体不可用:开关降级纯 DB 模式(限 2000 QPS,条件扣减直扣);③DB 也不可用:快速失败返回「太火爆」,优于挂起拖垮全站。

选型权衡:Redis 预扣+MQ vs DB 乐观锁直扣 vs 分布式锁+DB——前者吞吐高/削峰/链路长;后者链路短/强一致但万级 QPS 即瓶颈;分布式锁性能差有死锁风险,不推荐。

6.【关键技术点】
Redis Lua 原子脚本库存分段RocketMQ 顺序/事务/死信幂等(唯一索引+INSERT IGNORE)MySQL 条件扣减多级限流缓存预热最终一致性对账雪花算法服务隔离
7.【Java 实现示例】

① Lua 原子预扣:

-- KEYS[1] 库存段 key  KEYS[2] 已购用户 SET  ARGV[1] 用户ID  ARGV[2] 扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]) or '0')
if stock < tonumber(ARGV[2]) then return -1 end     -- 该段不足
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then return -2 end -- 重复购买
redis.call('DECRBY', KEYS[1], ARGV[2])
redis.call('SADD', KEYS[2], ARGV[1])
return tonumber(redis.call('GET', KEYS[1]))

② 秒杀服务(分层):

@Service
@RequiredArgsConstructor
public class SeckillServiceImpl implements SeckillService {
    private static final int SEG = 100, SEG_COUNT = 100;
    private final StringRedisTemplate redis;
    private final RocketMQTemplate mq;
    private final IdGenerator idGen;
    private final SeckillConfigCache configCache;
    private final RedisScript<Long> script =
        RedisScript.of(SeckillLua.SCRIPT, Long.class);

    public SeckillResult doSeckill(SeckillCmd cmd) {
        if (configCache.isSoldOut(cmd.activityId(), cmd.skuId()))
            return SeckillResult.reject("已售罄");
        String rateKey = "sec:rate:%d:%d".formatted(cmd.activityId(), cmd.userId());
        if (Boolean.FALSE.equals(redis.opsForValue()
                .setIfAbsent(rateKey, "1", Duration.ofSeconds(5))))
            return SeckillResult.reject("操作过于频繁");
        long orderId = idGen.nextId();
        Long remain = null;
        for (int i = 0; i < 3; i++) {
            int seg = ThreadLocalRandom.current().nextInt(SEG_COUNT);
            remain = redis.execute(script,
                List.of(stockKey(cmd, seg), boughtKey(cmd)),
                String.valueOf(cmd.userId()), "1");
            if (remain != null && remain >= 0) break;
            if (remain != null && remain == -2L)
                return SeckillResult.reject("每人限购 1 件");
        }
        if (remain == null || remain < 0) {
            configCache.markSoldOut(cmd.activityId(), cmd.skuId());
            return SeckillResult.reject("已售罄");
        }
        SeckillOrderMsg msg = new SeckillOrderMsg(orderId, cmd.userId(),
            cmd.skuId(), cmd.activityId(), System.currentTimeMillis());
        mq.asyncSend("SECKILL_ORDER_TOPIC",
            MessageBuilder.withPayload(msg)
                .setHeader(RocketMQHeaders.KEYS, String.valueOf(orderId)).build(),
            new SendCallback() {
                public void onSuccess(SendResult r) {}
                public void onException(Throwable e) { compensateStock(cmd, segOf(orderId)); }
            });
        return SeckillResult.queuing(orderId);
    }
}

③ 消费端幂等落库 + DB 条件扣减:

@RocketMQMessageListener(topic = "SECKILL_ORDER_TOPIC",
    consumerGroup = "seckill-order-cg", consumeMode = ConsumeMode.ORDERLY,
    maxReconsumeTimes = 3)
public class SeckillOrderConsumer implements RocketMQListener<SeckillOrderMsg> {
    @Transactional(rollbackFor = Exception.class)
    public void onMessage(SeckillOrderMsg msg) {
        int ins = orderMapper.insertIgnore(SeckillOrder.builder()
            .orderId(msg.orderId()).userId(msg.userId()).skuId(msg.skuId())
            .activityId(msg.activityId()).status(OrderStatus.CREATED).build());
        if (ins == 0) return;               // 重复消息,幂等丢弃
        int up = skuStockMapper.deductStock(msg.skuId(), 1); // WHERE stock>=1
        if (up == 0) { compensateRedis(msg); throw new IllegalStateException("no stock"); }
        orderMapper.insertOutbox(OutboxEvent.of("SECKILL_ORDER_CREATED", msg.orderId()));
    }
}
// Mapper: 条件扣减,天然防超卖
@Update("UPDATE sku_stock SET stock=stock-#{num},version=version+1 " +
        "WHERE sku_id=#{skuId} AND stock>=#{num}")
int deductStock(@Param("skuId") Long skuId, @Param("num") int num);
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 用 synchronized/ReentrantLock 锁库存——单机锁跨 200 副本无效,必超卖。正确:Redis Lua 或 DB 条件更新。
  • ❌ SELECT→UPDATE 判断库存——读写竞态必超卖。正确:UPDATE ... WHERE stock>0 看影响行数。
  • ❌ 扣 Redis 后同步写库并用 @Transactional 包整个秒杀方法——事务含 Redis/MQ 不原子,且不必要拉长连接占用。正确:预扣后立即返回,异步落库。
  • ❌ 库存只放 Redis 不落库——宕机丢失无法对账。正确:DB 权威 + 对账。
  • ❌ 「加机器就能扛」——忽略热点 Key 单点上限。正确:先打散热点再扩容。
10.【架构师评分标准】
初级 0~40
只说「Redis+MQ」,说不清防超卖原子性;把 SELECT→UPDATE 当扣库存;不知热点 Key。
中级 40~60
说出 Redis 预扣+异步+唯一索引幂等、Lua/分布式锁,但无量化和降级对账。
高级 60~80
完整四层架构、Lua 扣减、DB 条件兜底、幂等与可靠性、给 QPS/TPS、知 Redis 降级路径。
架构师 80~100
再加:①热点分段/本地令牌;②少卖vs超卖取舍+对账回补;③容量模型(50万→5万→1.2万→3000 TPS 逐级收敛依据);④降级开关+物理隔离;⑤成本视角(CDN 削 90% 流量 < 加服务器);⑥可观测性(拦截率/各环 QPS/MQ 积压/P99 大盘)。

第 2 题:商品详情页——千万级 SKU 热点数据多级缓存 缓存

1.【真实企业业务场景】
平台 1000 万 SKU,商品详情页峰值读取 30 万 QPS(大促翻倍)。详情聚合 12 类数据:基础信息、价格、库存、促销、评价、店铺、物流、推荐、SKU 规格、券、直播状态、埋点配置,平均 RT 需 < 200ms。MySQL 单库上限 1.5 万读 QPS、Redis 集群 60 万读 QPS。「iPhone 新品」等少量商品占全站 40% 流量,且库存每 100ms 变动。
2.【面试官问题】
    1. 为什么一次详情要查 12 个服务,怎么聚合?2. 多级缓存如何设计?3. 热点商品怎么处理?4. 库存每 100ms 变,缓存怎么不脏读?5. 缓存与 DB 一致性方案?6. 某个下游(如评价服务)慢,详情页要不要跟着慢?7. 缓存击穿怎么办?
3.【候选人的标准回答】

第一步:分析。详情页是读多写少、聚合型、容忍短暂不一致的典型。核心矛盾:12 个异构数据源的高扇出 vs 200ms RT,以及库存类强时效数据 vs 缓存。

第二步:挑战。扇出爆炸(串行 12 次→RT 翻倍)、热点倾斜(少量 SKU 占 40%)、强时效库存、下游故障传染、缓存击穿。

第三步:架构。动静分离:标题/图文/规格等静态部分 CDN 化(可缓存 5min);②多级缓存:浏览器→CDN→Nginx 本地缓存→应用本地 Caffeine→Redis 集群→DB;③并行聚合:CompletableFuture 并行调 12 个服务,取最快 N-1 + 兜底;④热点探测:Redis 访问计数+本地 ZooKeeper 广播,热点 SKU 推本地缓存并加多副本;⑤库存强时效:库存走独立短 TTL(如 200ms)+ 变更主动失效。

第四步:选型。Caffeine(进程内,亚毫秒,扛本地热点);Redis(集群共享,穿透兜底);Canal 订阅 Binlog 主动失效(替代轮询);OpenFeign + CompletableFuture(并行聚合)。

第五步:一致性。价格/库存:Binlog→Kafka→本地缓存失效 + Redis DEL,保证变更秒级生效;其余详情:TTL 5min + 写时主动失效。

第六步:高可用。下游慢/挂:舱壁隔离 + 超时降级(评价/推荐不可用时用空/默认,不阻塞主路径);熔断兜底返回旧缓存。

第七步:优化。热点本地缓存;合并相同 SKU 的并发回源(singleflight);详情页静态化预渲染;图片走对象存储+CDN。

4.【架构设计】
浏览器缓存 ▼ CDN(静态图文/规格,5min) ▼ Nginx 本地缓存(热点 SKU 详情,1s) ▼ 商品聚合服务 ├ Caffeine 本地缓存(热点 SKU,200ms~5min 分级 TTL) ├ singleflight 合并回源 └ CompletableFuture 并行调 12 个域服务(带超时/降级) ▼ Redis Cluster(详情快照 / 库存短 TTL) ▼ MySQL + Canal(Binlog)→Kafka→缓存主动失效
5.【技术方案深度解析】

为什么多级缓存?越靠近用户越快越便宜:本地缓存亚毫秒且零网络、Redis 共享但需网络、DB 最权威最慢。全走 Redis 仍可能被热 Key 打爆,本地缓存可扛住 90% 热点读。

库存 100ms 变怎么防脏读?库存单独短 TTL(如 200ms)或用版本号/时间戳校验;写时 Canal 订阅 Binlog 主动 DEL/更新,避免轮询。注意:详情页展示的「剩余库存」允许 ±几百的短暂偏差(用户感知无碍),下单时才走实时 Redis 校验。

一致性方案权衡:Cache-Aside(读穿写失效)实现简单、最终一致,适本题;延迟双删解决「写后立刻读」脏窗口;Binlog 订阅(Canal)解决「多端缓存失效遗漏」,最彻底但引入复杂度。

下游慢传染:舱壁隔离(每个下游独立线程池)+ 超时(如 80ms)+ 降级(返回默认/空),保证评价服务挂了详情页仍 200ms 返回。这正是「核心路径不依赖非核心服务」。

缓存击穿:热点 Key 失效瞬间大量请求穿透。用 singleflight(只放一个回源,其余共享结果)+ 热点 Key 永不过期(逻辑过期,后台刷新)+ 互斥锁重建。

6.【关键技术点】
多级缓存Caffeine动静分离/CDNCompletableFuture 并行聚合舱壁隔离/降级singleflightCanal Binlog 失效热点探测缓存击穿
7.【Java 实现示例】
// 并行聚合 12 个域,任一超时/异常走降级,主路径不阻塞
public ItemDetailVO getDetail(Long skuId) {
    CompletableFuture<BaseVO> f1 = supplyAsync(() -> baseSvc.get(skuId), basePool);
    CompletableFuture<PriceVO> f2 = supplyAsync(() -> priceSvc.get(skuId), pricePool);
    CompletableFuture<StockVO> f3 = supplyAsync(() -> stockSvc.realTime(skuId), stockPool);
    CompletableFuture<ReviewVO> f4 = supplyAsync(() -> reviewSvc.top(skuId), reviewPool)
        .exceptionally(ex -> ReviewVO.EMPTY);   // 评价降级
    CompletableFuture<List<ItemDetailVO>> all =
        CompletableFuture.allOf(f1, f2, f3, f4).thenApply(v ->
            List.of(f1.join(), f2.join(), f3.join(), f4.join()));
    try {
        return assemble(all.get(200, TimeUnit.MILLISECONDS)); // 整体超时 200ms
    } catch (TimeoutException e) {
        return assembleFromCache(skuId);   // 降级:返回旧缓存快照
    }
}
// singleflight:同一 skuId 并发只回源一次
public String singleflightLoad(Long skuId, Function<Long,String> loader) {
    CompletableFuture<String> f = inFlight.computeIfAbsent(skuId,
        k -> CompletableFuture.supplyAsync(() -> loader.apply(k))
            .whenComplete((r, e) -> inFlight.remove(skuId)));
    return f.join();
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 串行 for 循环调 12 个服务——RT 叠加 1s+。正确:CompletableFuture 并行。
  • ❌ 所有数据统一缓存 30min——库存脏读。正确:分级 TTL + 主动失效。
  • ❌ 下游挂了详情页也挂——未做舱壁降级。正确:隔离+超时+默认。
  • ❌ 用 Redis 当唯一缓存被热 Key 打爆——缺本地缓存。正确:多级。
10.【架构师评分标准】
初级 0~40
只会加 Redis 缓存;不知多级缓存、并行聚合。
中级 40~60
说得出 Redis+本地缓存+降级,但无分级 TTL、无并行聚合细节。
高级 60~80
多级缓存+并行聚合+热点探测+Binlog 失效+舱壁降级,给 RT/QPS。
架构师 80~100
再加:①动静分离与 CDN 化策略;②强时效字段(库存)独立方案与偏差可解释;③singleflight 防击穿与逻辑过期;④成本(命中率指标 >95%、回源比);⑤可观测(各层命中率、P99、下游 SLA 大盘)。

第 3 题:大促限流——从网关到资源的多级流量防护体系 限流

1.【真实企业业务场景】
大促峰值入口流量 80 万 QPS,其中 60% 是爬虫/无效请求,核心交易集群只能稳定承接 8 万 QPS。要求:核心下单接口保护、非核心接口(搜索/推荐)可限流丢弃、单用户刷单拦截、集群总配额可控、热点接口独立保护。已用 Spring Cloud Gateway + Sentinel + Nacos,需设计完整限流体系。
2.【面试官问题】
    1. 限流分哪几层?每层限什么?2. 令牌桶 vs 漏桶怎么选?3. Sentinel 和 Gateway 限流区别?4. 集群限流怎么做?5. 单用户维度限流怎么实现且不误杀?6. 限流后请求怎么处理(丢弃/排队/降级)?7. 限流阈值怎么定?
3.【候选人的标准回答】

第一步:分析。限流目标是保护系统不被超过处理能力的流量打垮,本质是「在成本边界内最大化有效吞吐」。80 万入口、8 万承接,需层层收敛 90%。

第二步:挑战。入口无效流量 60%、单实例限流无法控全局、用户刷单、阈值难定、误杀正常用户。

第三步:多级限流架构。接入层(Nginx/SLB):连接数+并发限制,挡 DDoS;②网关层:全局 QPS 令牌桶 + 路由级限流(核心 8 万、非核心可丢)+ 用户维度配额;③应用层:Sentinel 资源级(热点参数/接口)限流 + 线程池隔离;④资源层:DB/Redis 连接池上限 + 信号量。

第四步:选型。Gateway 令牌桶(入口粗粒度);Sentinel(热点参数、熔断降级、集群流控);Redis + Lua(用户维度精确计数);Nacos 动态下发阈值。

第五步:一致性/正确性。集群限流用中心计数器(Redis INCR 窗口计数)或 Sentinel 集群流控(Token Server)。

第六步:高可用。限流组件自身须高可用:Sentinel 控制台/Token Server 多副本;Redis 计数用本地兜底(Redis 挂则退化为单机令牌桶)。

第七步:优化。阈值基于压测 + 历史容量模型动态调;非核心接口限流直接返回默认(降级),核心接口返回「排队中」并走 MQ 排队。

4.【架构设计】
Nginx/LVS(连接数限流、防 DDoS) ▼ API Gateway(全局 QPS 令牌桶 + 路由级 + 用户维度配额) ▼ Sentinel(资源级限流 + 热点参数 + 熔断降级) ├ 集群流控:Token Server(Redis 计数兜底) ▼ 业务服务(线程池隔离 + 信号量) ▼ DB/Redis(连接池上限 = 最后一道防线)
5.【技术方案深度解析】

令牌桶 vs 漏桶:令牌桶允许突发(桶内攒的令牌一次性放行),适合有短时尖峰的 Web 请求;漏桶强制恒定速率,平滑输出,适合保护后端稳态处理能力。网关入口常用令牌桶(允许合理突发),保护 DB 的资源层可用漏桶/信号量(恒定)。

Gateway vs Sentinel:Gateway 限流在路由/IP/Header 维度,粗粒度、入口第一道;Sentinel 在方法/资源/热点参数维度,细粒度、可熔断降级、支持集群流控。两者互补:Gateway 挡量,Sentinel 控质。

集群限流:单机令牌桶无法控全局(N 台×单机阈值≠集群阈值)。方案:①Sentinel 集群流控(Token Server 统一发牌);②Redis 滑动窗口计数(INCR+过期)做全局配额。后者简单但需关注 Redis RT,故本地加一档单机兜底。

用户维度限流不误杀:用 Redis 滑动窗口(如 1s 内同一 userId ≤ 5 次),SET NX+TTL 或 ZSET 去重;区分「合法突发」与「脚本刷单」:结合设备指纹、行为序列(如 1ms 间隔连续点击)识别脚本,命中直接进黑名单而非简单 429。

限流后处理:核心交易→返回「排队中」+MQ 排队(削峰);非核心(搜索/推荐)→直接返回默认/降级内容(快速失败,不占资源);前端配合重试退避,避免雪崩式重放。

6.【关键技术点】
令牌桶/漏桶SentinelGateway 限流集群流控滑动窗口计数热点参数限流熔断降级动态阈值(Nacos)
7.【Java 实现示例】
// 用户维度滑动窗口限流(Redis + Lua,原子校验+计数)
private static final String SCRIPT = """
    -- KEYS[1]=user:limit:{id}  ARGV[1]=now(ms) ARGV[2]=window(ms) ARGV[3]=max
    redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1]-ARGV[2]);
    local cnt = redis.call('ZCARD', KEYS[1]);
    if cnt >= tonumber(ARGV[3]) then return 0 end
    redis.call('ZADD', KEYS[1], ARGV[1], ARGV[1]);
    redis.call('PEXPIRE', KEYS[1], ARGV[2]);
    return 1""";
// Sentinel 资源限流 + 热点参数(skuId 维度)
@SentinelResource(value = "createOrder", blockHandler = "orderBlocked",
    fallback = "orderFallback")
public OrderVO create(OrderCmd cmd) { return orderSvc.create(cmd); }

public OrderVO orderBlocked(OrderCmd cmd, BlockException e) {
    return OrderVO.queuing(); // 排队提示,前端转 MQ 异步
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 只在应用层加 @SentinelResource——入口 80 万已打到网关,应用早被连接打满。正确:网关先挡。
  • ❌ 用 synchronized 计数限流——单 JVM 无效。正确:Redis/集群流控。
  • ❌ 限流阈值拍脑袋写死——大促必然误杀或击穿。正确:压测+容量模型+动态。
  • ❌ 限流后无限重试——重放雪崩。正确:前端退避/排队提示。
10.【架构师评分标准】
初级 0~40
只会加个 @SentinelResource;不知多级与集群。
中级 40~60
网关+Sentinel 两层,知令牌桶,但无集群、无阈值依据。
高级 60~80
四级限流、令牌桶/漏桶选型、集群流控、用户维度、降级处理、阈值来自压测。
架构师 80~100
再加:①限流后差异化处理(排队 vs 快速失败);②防误杀与防绕过(设备指纹/白名单);③动态阈值(Nacos 热更新+容量模型);④成本(限流省下的机器);⑤可观测(被限流率、各层 QPS 大盘)。

第 4 题:优惠券/红包高并发领取——金额拆分、库存扣减与防刷 高并发

1.【真实企业业务场景】
春节红包活动:发放总额 1 亿元、红包个数 1000 万个(随机金额 1 元~8888 元),活动开始 1 分钟内预计 200 万用户抢领,峰值 40 万 QPS。要求:不超发总额、不重复领取、金额随机且公平、防脚本薅羊毛、红包 24h 内有效可核销。
2.【面试官问题】
    1. 总额 1 亿、1000 万个红包怎么保证不超发?2. 金额随机怎么生成才公平且预知总数?3. 库存扣减与秒杀有何不同?4. 重复领取怎么防?5. 防刷怎么做?6. 红包预拆好还是实时拆?
3.【候选人的标准回答】

第一步:分析。红包 = 有限资源(个数)+ 有限总额(金额)+ 随机分配 + 防重 + 防刷。比秒杀多一个「金额拆分」维度,且「不超发总额」是另一条硬约束。

第二步:挑战。总额与个数双约束、随机金额可解释、并发预扣、防重防刷、公平。

第三步:架构。预拆分:活动前用「二倍均值法/线段切割」离线生成 1000 万个红包金额,写入 Redis 队列(List/Stream),总额固定;②领取:Redis LPOP/XREAD 原子取一个金额 + SET NX 记录 userId 防重;③异步落库:消息写红包领取记录 + 账户加款(事务消息保证);④防刷:用户维度限流 + 设备指纹 + 行为风控。

第四步:选型。Redis List/Stream(预拆分池,原子取);SET NX(防重);RocketMQ 事务消息(领取+加款一致);规则引擎(风控)。

第五步:一致性。预拆分定总额,领取只减池;加款失败则回补池;对账:已领金额之和 + 池剩余 = 1 亿。

第六步:高可用。Redis 集群多副本;领取失败重试;加款走账户核心服务(幂等)。

第七步:优化。预拆分避免实时随机导致总额不可控;分片池(按用户 hash)打散;本地缓存防重 bloom。

4.【架构设计】
风控/防刷(设备指纹、行为、限流) ▼ 领取服务 ├ Redis SET NX:userId 防重复领取 └ Redis List/Stream:LPOP 原子取出预拆分金额 ▼ RocketMQ 事务消息(半消息→本地记账→Commit) ▼ 账户服务(幂等加款 + 红包记录落库) ▼ 对账:已领金额 + 池剩余 == 1 亿(定时校验)
5.【技术方案深度解析】

为什么预拆分而非实时随机?实时随机(如每次 rand 1~剩余)无法保证总额恰好 1 亿且末尾易极端(最后一个拿走全部剩余)。预拆分用「二倍均值法」:每次在 [0.01, 2×剩余均值] 取,保证均值稳定、总额精确、分布合理,且领取是 O(1) 原子出队,性能极高。

与秒杀区别:秒杀抢的是「同一件商品」(库存共享,需 Lua 复合判断);红包抢的是「池里独立一份」(金额各异,LPOP 即扣,天然不超发、无竞争)。所以红包更简单——不需要 Lua 复合,但需要总额预知防重

防超发总额:池里只有 1000 万个、总额 1 亿,取出一个就少一个,不可能超;加款失败回补池,保证「已领+剩余=总额」。

防重复:SET NX(userId→红包id,24h TTL),LPOP 与 SET NX 需原子——用 Lua 包成一条脚本,避免「取出但防重失败」导致丢红包。

防刷:设备指纹(同一设备多账号)、行为序列(毫秒级连点)、IP 聚集、新注册账号限制;命中进黑名单 + 风控拦截,而非仅限流。

6.【关键技术点】
红包预拆分(二倍均值法)Redis List/Stream LPOPSET NX 防重Lua 原子领取事务消息设备指纹/风控防刷对账
7.【Java 实现示例】
// 预拆分:二倍均值法,保证总额精确、分布合理
public List<Long> split(long total, int count) { // 单位:分
    List<Long> res = new ArrayList<>(count);
    long remain = total, left = count;
    for (int i = 0; i < count - 1; i++, left--) {
        long max = remain * 2 / left;          // 二倍均值上限
        long amt = 1 + ThreadLocalRandom.current().nextLong(max - 1);
        res.add(amt); remain -= amt;
    }
    res.add(remain); return res;
}
// 原子领取:LPOP 取金额 + SET NX 防重,一条 Lua
private static final String CLAIM = """
    if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then return -1 end
    local v = redis.call('LPOP', KEYS[1]); if not v then return -2 end
    redis.call('SADD', KEYS[2], ARGV[1]);
    return v""";
// 账户加款(幂等):事务消息保证领取记录与加款一致
@RocketMQTransactionListener
public class RedPacketListener implements RocketMQLocalTransactionListener {
    public RocketMQLocalTransactionState executeLocal(Message m, Object a) {
        return accountSvc.add((RedPacketMsg) a) ? COMMIT : ROLLBACK;
    }
    public RocketMQLocalTransactionState checkLocal(Message m) {
        return accountSvc.exists(((RedPacketMsg) a).id()) ? COMMIT : UNKNOWN;
    }
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 实时随机金额不设上限——总额失控/末尾极端。正确:预拆分。
  • ❌ LPOP 与 SET NX 分开调用——可能丢红包(取出防重失败)。正确:Lua 合并。
  • ❌ 只靠限流防刷——黄牛换 IP/设备。正确:设备指纹+行为风控。
  • ❌ 领取与加款非事务——可能领了不加款或加了重复。正确:事务消息/幂等。
10.【架构师评分标准】
初级 0~40
实时 rand 金额;不懂双约束与防重。
中级 40~60
说预拆分+SET NX 防重,但不懂原子领取与对账。
高级 60~80
预拆分+Lua 原子领取+事务消息+对账+防刷,给 QPS/总额约束。
架构师 80~100
再加:①不公平性与分布可调;②池丢失重建策略;③风控多因子与误杀平衡;④成本(预拆分内存/带宽);⑤可观测(领取率/防刷拦截率/对账差异)。

第 5 题:热点库存分片——Redis 热 Key 与库存分段扣减 热Key

1.【真实企业业务场景】
某爆款 SKU 活动库存 10 万件,但 70% 请求集中在该 SKU,Redis 单分片上限 10 万 QPS,活动峰值该 SKU 读写 35 万 QPS。直接单 Key 存储导致该分片 CPU 100% 成为瓶颈。要求在不改 Redis 集群规模下治理热 Key,并保证扣减正确。
2.【面试官问题】
    1. 热 Key 怎么发现?2. 单 Key 为什么扛不住?3. 库存分段怎么设计?4. 分段后总库存怎么聚合与对账?5. 如何避免「段内为 0 但总库存还有」误判售罄?6. 本地缓存/本地内存令牌能否彻底解决?
3.【候选人的标准回答】

第一步:分析。热 Key 根因是同一 Key 的全部流量落同一分片单线程,集群并行度被锁死在单分片。解决思路:把单 Key 拆成多个 Key,使流量 hash 到不同分片/实例。

第二步:挑战。单分片吞吐上限、聚合准确性、误售罄、本地缓存一致性。

第三步:分段架构。①库存 10 万分成 N 段(如 100 段×1000);②每段独立 Key 落在不同槽位;③扣减时轮询/一致性 Hash 选段执行 Lua;④总库存 = 各段和,售罄 = 所有段为 0;⑤热点进一步下沉:本地内存令牌桶(Caffeine 预分配段额度),Redis 只做最终兜底。

第四步:选型。Redis Cluster(槽位分散);一致性 Hash(请求均匀分布);本地 Caffeine(扛本地热点);监控(Redis 热点 Key 探测 / Proxy 统计)。

第五步:一致性。分段扣减用 Lua 保证段内原子;总库存聚合读多段之和(或维护一个汇总 Key 异步更新);对账:汇总 Key + 各段和 == DB 库存。

第六步:高可用。某分片挂→轮询下一段绕过;本地令牌与 Redis 差额定时回补。

第七步:优化。热 Key 探测自动触发本地缓存;客户端分片缓存路由;减少大价值 Key。

4.【架构设计】
请求流量(35 万 QPS,单 SKU) ▼ 库存路由层(一致性 Hash / 轮询选段) ├ 本地内存令牌(Caffeine,预分配 1000/段,亚毫秒) ▼ Redis Cluster ├ sec:stock:{sku}:seg0 ... seg99(分布不同槽位/分片) └ sec:soldout:{sku}(段全 0 才置位) ▼ 汇总 + 对账任务(各段和 == DB 库存)
5.【技术方案深度解析】

为什么单 Key 扛不住?Redis 单分片单线程处理命令,约 10 万 QPS 上限;35 万流量全落该分片 → 命令排队、RT 飙升、CPU 100%,其余分片闲置。这是分布式系统中的「倾斜」反模式。

分段设计:key 由 sec:stock:{sku}:{seg} 组成,seg 参与 CRC16 槽位计算,天然分散到不同分片。扣减时随机或 Hash 选段,命中空段再换段(最多重试 K 次),把 35 万 QPS 摊到 100 个 Key×多分片。

避免误售罄:不能只看某一段为 0 就判售罄。需「遍历所有段都 ≤0 才售罄」,或用汇总 Key + 各段增量。生产中常用本地标记+异步聚合:某段为 0 时本地记该段空,所有段空才全局售罄。

本地内存令牌能否彻底解决?能扛绝大部分读(亚毫秒、零网络),但不是真相源——多实例本地额度需协调,否则总超发。工程实践:本地令牌做「粗筛/加速」,Redis 做「精确裁判」,DB 做「最终兜底」,三层递减。

权衡:分段增加聚合与对账复杂度、轮询重试增加少量 RT;但换来集群吞吐线性提升,是热 Key 治理的必选项。

6.【关键技术点】
热 Key 探测库存分段一致性 Hash本地内存令牌(Caffeine)Lua 段内原子汇总与对账多级兜底
7.【Java 实现示例】
// 一致性 Hash 选段,避免请求倾斜到固定分片
public int pickSegment(long skuId, long userId, int segCount) {
    return Hashing.consistentHash(
        Hashing.murmur3_128().hashLong(skuId * 31 + userId), segCount);
}
// 段内 Lua 扣减(同第1题),外层轮询重试
public SeckillResult claim(long skuId, long userId) {
    for (int i = 0; i < 4; i++) {
        int seg = pickSegment(skuId, userId, SEG);
        Long r = redis.execute(script, List.of(segKey(skuId, seg), boughtKey(skuId)),
            userId + "", "1");
        if (r != null && r >= 0) return queuing();
        if (r != null && r == -2L) return reject("限购"); // 重复
        // r==-1 该段空,换段重试
    }
    markSoldOut(skuId); return reject("已售罄");
}
// 本地令牌桶:预分配段额度,扛本地热点(每实例 ~1000/段)
LoadingCache<Long, AtomicLong> localTokens = Caffeine.newBuilder()
    .build(skuId -> new AtomicLong(preloadFromRedis(skuId)));
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 加 Redis 节点解决热 Key——单 Key 仍落单分片,无效。正确:分片/本地。
  • ❌ 只看一段为 0 判售罄——误售罄损失 GMV。正确:全段聚合。
  • ❌ 只用本地内存——多实例超发。正确:本地粗筛+Redis 裁判+DB 兜底。
10.【架构师评分标准】
初级 0~40
不知热 Key 与单分片关系,只会加机器。
中级 40~60
说分段,但不懂槽位分散与误售罄。
高级 60~80
分段+一致性 Hash+Lua+聚合对账+轮询绕过,给 QPS 依据。
架构师 80~100
再加:①本地内存令牌三级兜底;②热 Key 自动探测与预热;③大 Key vs 热 Key 区分;④成本(不扩集群的优化);⑤可观测(各段 QPS/命中率/误售罄率)。

第 6 题:高并发下单——异步化削峰填谷与超时关单 异步

1.【真实企业业务场景】
普通电商下单峰值 8 万 TPS,但大促瞬时可达 25 万 TPS,订单库写上限 3 万 TPS。下单需同步校验库存、价格、优惠券、风控、用户、地址 6 个系统(P99 需 < 300ms)。未支付订单 30min 自动关单释放库存。要求:下单高峰不压垮 DB、下单结果快速返回、关单可靠不漏、库存不长期占用。
2.【面试官问题】
    1. 为什么下单要异步化?2. 同步校验那么多系统怎么不全同步?3. 异步下单后用户怎么拿结果?4. 超时关单怎么做才可靠不漏?5. 关单释放库存与超卖的关系?6. 消息堆积导致下单延迟过长怎么办?
3.【候选人的标准回答】

第一步:分析。下单是写密集 + 多系统校验 + 需快速响应。入口 25 万 TPS 远超库 3 万,必须削峰;但「下单成功」对用户体验即时,故采用预校验同步 + 落库异步

第二步:挑战。校验扇出、写峰值、关单可靠性与时效、库存占用释放、消息延迟。

第三步:架构。同步轻校验:库存预扣(Redis)、价格/风控(可降级)在网关后同步做,毫秒级;②异步落库:生成订单号即返回「提交中」,订单消息入 MQ,消费者匀速 3 万 TPS 落库;③结果查询:前端轮询/长连接/WebSocket 推订单状态;④关单:RocketMQ 延迟消息(30min)或定时任务扫描「待支付」表,触发关单 + 库存回补。

第四步:选型。RocketMQ(延迟消息/顺序/事务);Redis(库存预扣/订单状态缓存);时间轮/延迟队列(关单);Canal(订单 Binlog 同步到查询库)。

第五步:一致性。预扣库存与订单创建最终一致;关单成功回补库存;对账:待支付占用 + 已支付 = 总扣减。

第六步:高可用。MQ 堆积时消费者扩副本 + 提高并发;关单延迟消息失败重试 + 补偿扫描兜底(双保险);订单状态多副本。

第七步:优化。库存预热到 Redis;下单链路剥离非核心(积分/推荐异步);订单读写分离 + 分库分表;关单用延迟消息避免全表扫。

4.【架构设计】
下单请求 ▼ 网关(限流 + 用户/风控轻校验) ▼ 下单服务(同步:库存 Redis 预扣 + 价格校验;秒级返回订单号) ▼ 发 MQ RocketMQ(ORDER_CREATE_TOPIC,削峰 3 万 TPS) ▼ 订单消费者(落库 + 状态机 CREATED→PAID) ▼ 延迟消息(30min)→ 关单服务 → 回补库存 └ 定时补偿扫描(兜底,防延迟消息丢失)
5.【技术方案深度解析】

为什么异步?25 万 TPS 入口,3 万 TPS 库,若同步落库,连接池秒满、RT 飙升、连锁雪崩。异步把「用户感知」与「持久化」解耦:用户立刻拿到订单号(心理完成),落库由 MQ 匀速消化。

同步校验取舍:库存(强一致,必须同步预扣)、价格(可缓存 1s)、风控(可降级跳过)、用户/地址(缓存)。核心校验同步、非核心异步,平衡 RT 与正确性。

关单可靠性:只用延迟消息有丢失风险(Broker 宕机),故延迟消息 + 定时补偿扫描双保险:定时任务每分钟扫「待支付且创建>30min」订单关单,保证不漏;延迟消息做实时性。

与超卖关系:关单回补的是 Redis 预扣库存,使库存可继续卖;DB 物理库存在支付成功时才扣,故关单不影响 DB,只影响「逻辑库存」。

削峰 vs 延迟权衡:异步引入下单到可见的延迟(本题目标 <3s)。若 MQ 堆积,订单可见变慢,需监控积压并扩大消费并发;极端时前端提示「订单处理中」。

6.【关键技术点】
异步下单削峰填谷RocketMQ 延迟消息库存预扣/回补订单状态机补偿扫描读写分离幂等
7.【Java 实现示例】
// 关单:延迟消息 + 补偿扫描双保险
@RocketMQMessageListener(topic = "ORDER_DELAY_TOPIC", consumerGroup = "close-cg")
public class CloseOrderConsumer implements RocketMQListener<MessageExt> {
    public void onMessage(MessageExt m) {
        Order order = parse(m);
        if (order.status() == Status.UNPAID) {
            orderMapper.closeOrder(order.id());    // 状态机条件更新
            stockRedis.release(order.skuId(), order.num());
        }
    }
}
// 补偿:每分钟扫超时未支付,防止延迟消息丢失
@Scheduled(cron = "0 */1 * * * *")
public void compensate() {
    List<Order> list = orderMapper.listUnpaidOlderThan(30, TimeUnit.MINUTES);
    list.forEach(o -> { orderMapper.closeOrder(o.id()); stockRedis.release(o.skuId(), o.num()); });
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 同步落库扛 25 万 TPS——连接池爆。正确:异步削峰。
  • ❌ 只靠延迟消息关单——丢失风险。正确:双保险补偿。
  • ❌ 关单不回补库存——库存长期占用/少卖。正确:回补。
  • ❌ 全同步校验 6 系统——RT 爆炸。正确:核心同步非核心异步。
10.【架构师评分标准】
初级 0~40
同步落库;不懂削峰与关单。
中级 40~60
说异步+延迟消息关单,但无补偿与一致性。
高级 60~80
异步下单+预扣+延迟消息+补偿+回补,给 TPS 收敛。
架构师 80~100
再加:①同步/异步校验取舍;②状态机并发安全;③主从延迟应对;④积压应急;⑤成本(消费资源)与可观测(积压/关单率/P99)。

第 7 题:大促洪峰——全链路压测、容量规划与降级预案 稳定性

1.【真实企业业务场景】
年度大促目标峰值 120 万 QPS、GMV 同比 +50%。历史大促曾因「推荐服务慢→线程池耗尽→下单全挂」损失千万。要求:提前 2 周完成全链路压测、给出容量规划、准备降级/限流/熔断预案、活动当天有实时监控与一键止血。
2.【面试官问题】
    1. 全链路压测怎么做才真实?2. 容量怎么规划?3. 降级/熔断/限流的触发条件?4. 预案怎么演练?5. 活动当天出问题怎么止血?6. 压测数据怎么造不污染线上?
3.【候选人的标准回答】

第一步:分析。大促稳定性 = 容量充足 + 防御到位 + 预案可执行 + 可观测。事故往往不是单点,而是级联失效(一个慢依赖拖垮全链路)。

第二步:挑战。影子流量隔离、容量模型、级联雪崩、预案可执行性、实时监控。

第三步:架构。全链路压测:流量打标(shadow tag)+ 影子库/影子 topic,生产环境真实压测不影响真实数据;②容量规划:基于历史 QPS×增长系数×峰值因子,算各组件实例数与阈值(网关/应用/Redis/DB/带宽);③防御三层:限流(入口)、熔断(依赖)、降级(非核心);④预案:预案平台一键执行(开关/切流/降级),演练常态化;⑤监控:Prometheus+Grafana+SkyWalking,大促作战室大盘。

第四步:选型。全链路压测(自研/开源如流量回放);Sentinel(熔断降级);Nacos(开关);Prometheus/Grafana/SkyWalking(可观测)。

第五步:一致性/正确性。压测流量隔离(影子标),结果不落真实库;预案开关幂等可回滚。

第六步:高可用。多 AZ 部署;预案多级(限流→降级→切流→拒绝);混沌工程验证容错。

第七步:优化。容量留 30% 余量;非核心服务可整段降级释放资源给核心;自动弹性(HPA)。

4.【架构设计】
全链路压测(影子流量/影子库) ▼ 容量规划(QPS 模型 → 各组件实例数 + 阈值) ▼ 生产环境 ├ 限流(Gateway/Sentinel):入口保护 ├ 熔断(Sentinel):依赖慢/错 自动断开 └ 降级(Nacos 开关):非核心返回默认 ▼ 预案平台(一键执行/回滚) ▼ 监控大盘(Prometheus+Grafana+SkyWalking)+ 作战室
5.【技术方案深度解析】

全链路压测真实性:核心在影子流量——请求打标(如 header x-shadow: true),中间件识别后路由到影子库/影子 topic/影子缓存,与生产同环境同数据规模,但结果隔离。避免「只在测试环境压」导致容量失真。

容量规划模型:目标实例数 = 峰值QPS × 安全系数(1.3) / 单实例拐点QPS;各组件分别算(网关连接、应用线程、Redis 分片、DB 连接、机房带宽)。瓶颈在最低的水桶板。

三道防线触发:限流——入口超阈值;熔断——依赖错误率/RT 超 SLA(如错误率>50% 或 RT>1s 持续 N 秒);降级——依赖不可用或容量紧张时关闭非核心(推荐/评论/积分)。

预案可执行性:写进预案平台(开关/脚本),定期演练(红蓝对抗),避免「纸上预案」。活动当天按监控指标触发,一键止损。

级联失效根因:历史事故是「无隔离线程池 + 无超时 + 无熔断」→ 一个慢依赖占满线程 → 全链路阻塞。解法:舱壁隔离 + 超时 + 熔断 + 降级四件套。

6.【关键技术点】
全链路压测影子流量容量规划模型限流/熔断/降级舱壁隔离预案平台可观测(SkyWalking)混沌工程
7.【Java 实现示例】
// 依赖调用隔离 + 超时 + 熔断(Resilience4j 示例)
Supplier<ReviewVO> call = () -> reviewClient.top(skuId);
ReviewVO r = Resilience4j.wrap(call)
    .withBulkhead(Bulkhead.of("review", config))      // 舱壁:独立信号量
    .withTimeLimiter(3, TimeUnit.SECONDS)              // 超时
    .withCircuitBreaker(CircuitBreaker.of("review", cbConfig)) // 熔断
    .fallback(ex -> ReviewVO.EMPTY)                    // 降级
    .get();
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 只在测试环境压测——容量失真。正确:全链路影子压测。
  • ❌ 预案写在文档里没演练——出事不会用。正确:平台化+演练。
  • ❌ 所有依赖共用线程池——级联雪崩。正确:舱壁隔离。
  • ❌ 只加机器不降级——成本爆炸且仍有单点。正确:降级+弹性。
10.【架构师评分标准】
初级 0~40
只会加机器;不知压测与预案。
中级 40~60
说限流降级,但无限容量模型与演练。
高级 60~80
全链路压测+容量规划+三道防线+预案+可观测,给模型。
架构师 80~100
再加:①级联失效根因与四件套;②影子流量隔离细节;③成本优化(弹性/临时扩容);④混沌工程验证;⑤作战室与决策机制;⑥可观测黄金指标。

第 8 题:实时计数与排行榜——UV 统计与 TopN 榜单 计数

1.【真实企业业务场景】
短视频平台:每日播放 50 亿次,需实时统计「视频播放量」「直播间在线人数」「商品点击 UV」「小时榜 Top100」。UV 要求去重(同一用户 1 天只计 1 次),榜单更新延迟 < 1s,大促实时大屏要秒级刷新。存储成本敏感。
2.【面试官问题】
    1. 播放量(可重复)和 UV(去重)用什么结构?2. HyperLogLog 精度与代价?3. 在线人数怎么算(连接 vs 计数)?4. TopN 榜单用什么(ZSet vs 外部排序)?5. 实时大屏高并发读怎么扛?6. 数据量太大存储成本怎么控?
3.【候选人的标准回答】

第一步:分析。计数场景分两类:可重复计数(播放量,INCR 即可)与去重计数(UV,需 Set/HLL);榜单是有序实时排名(ZSet)。关键是精度 vs 成本 vs 延迟的权衡。

第二步:挑战。UV 去重存储、在线人数准确性、榜单实时性、大屏高读、成本。

第三步:架构。①播放量:Redis INCR(分片 key 按视频+小时);②UV:HyperLogLog(误差 0.81%,内存固定 12KB/key)做近似,精确场景用 Bitmap(日活 < 用户数且稀疏时省);③在线人数:WebSocket 连接数 + Redis 计数(进房 INCR/离房 DECR,心跳保活);④TopN:ZSet ZINCRBY+ZREVRANGE;⑤大屏:ZSet/聚合结果缓存 + 定时刷新(1s)+ CDN。

第四步:选型。Redis(INCR/HLL/ZSet);Bitmap(精确 UV 小范围);Flink(实时流聚合,分钟级→秒级);ClickHouse(离线榜单/回放)。

第五步:一致性。计数最终一致可接受;UV 近似可接受;大屏展示允许 ±1s 延迟。

第六步:高可用。Redis 集群;HLL/ZSet 持久化;Flink 检查点容错。

第七步:优化。key 按时间分片避免大 Key;HLL 合并(每日 PFCOUNT 汇总);榜单缓存 1s 降低读压;冷数据归档 ClickHouse。

4.【架构设计】
上报流量(播放/点击/心跳) ▼ 接入层(聚合/采样) ▼ Flink 实时流(窗口聚合,秒级) ▼ Redis ├ INCR:播放量(分片) ├ HLL:UV 去重(12KB/key) ├ ZSet:TopN 榜单 └ Bitmap:精确 UV(小范围) ▼ 大屏/榜单 API(1s 缓存 + CDN) / 离线 ClickHouse
5.【技术方案深度解析】

HLL 精度与代价:标准 HLL 误差 ~0.81%,内存恒为 ~12KB,无论 UV 是 1 万还是 10 亿。适合「大致知道量级」的 UV(DAU/在线)。若要 100% 精确,用 Bitmap(1 用户 1 bit,1 亿用户 ~12MB/天,稀疏时反而不省),或 Set(精确但内存爆炸,不可用)。

在线人数:连接数 ≠ 真实在线(断线未感知)。用「进房 INCR + 心跳续期 + 离房/超时 DECR」维护计数,配合 WebSocket 连接做校验;超时(如 30s 无心跳)清理。

TopN 榜单:ZSet 的 ZINCRBY 实时更新分数、ZREVRANGE 取 Top100 是 O(log N),远优于每次全量排序。注意大 Key(一个热门榜单几百万成员),可分段+合并或只保留 TopK 窗口。

大屏高读:榜单/计数结果写入缓存并 1s 刷新,读走缓存 + CDN,避免每次查 Redis 聚合;多用户看同一大屏,缓存命中率极高。

成本:HLL 用空间换精度;Bitmap 压缩;冷数据按天归档到 ClickHouse 做历史榜,Redis 只留热数据。

6.【关键技术点】
Redis INCRHyperLogLogBitmapZSet 排行榜Flink 实时聚合在线人数计数ClickHouse 归档
7.【Java 实现示例】
// UV 去重(HLL 近似)
redis.opsForHyperLogLog().add("uv:video:" + videoId + ":" + date, userId);
long uv = redis.opsForHyperLogLog().size("uv:video:" + videoId + ":" + date);
// 精确 UV 小范围(Bitmap,按 offset=userId)
redis.opsForValue().setBit("uvbit:" + date, userId, true);
// TopN 榜单
redis.opsForZSet().incrementScore("rank:hour:" + hour, videoId, 1);
Set<ZSetOperations.TypedTuple<String>> top =
    redis.opsForZSet().reverseRangeWithScores("rank:hour:" + hour, 0, 99);
// 在线人数:进房 +1 / 心跳续期 / 离房 -1
redis.opsForValue().increment("online:live:" + roomId);
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ UV 用 Set 存 userId——内存爆炸。正确:HLL/Bitmap。
  • ❌ 每次查榜单都全量排序——RT 高。正确:ZSet 增量维护。
  • ❌ 在线人数只看连接数——含僵尸。正确:心跳计数。
  • ❌ 大屏每次查实时聚合——读压爆。正确:缓存 1s。
10.【架构师评分标准】
初级 0~40
UV 用 Set;不知 HLL/ZSet。
中级 40~60
说 HLL 和 ZSet,但不懂精度/成本权衡。
高级 60~80
HLL/ZSet/Bitmap 选型 + 在线人数 + 大屏缓存 + 成本,给量级。
架构师 80~100
再加:①精度/成本/延迟三角权衡;②Flink 实时流定位;③大 Key 治理;④冷热归档(ClickHouse);⑤可观测(计数延迟/误差率)。

第 9 题:库存中心——强一致防超卖、对账与回补 一致性

1.【真实企业业务场景】
平台建统一「库存中心」服务,管理 1000 万 SKU、总库存 5 亿件,上游有秒杀、普通下单、拼团、预售 4 类扣减,下游对接订单/履约/财务。曾发生「Redis 扣了但 DB 没扣,对账发现少 2000 件」与「超卖 50 件赔偿」。要求:统一库存语义、防超卖、可审计、能对账回补。
2.【面试官问题】
    1. 库存中心为什么要独立?2. 扣减语义怎么统一(预扣/实扣/回补)?3. 强一致 vs 最终一致怎么选?4. 对账怎么做?5. 少卖/超卖分别怎么处理?6. 多机房库存怎么同步?
3.【候选人的标准回答】

第一步:分析。库存是多业务共享的核心资源,分散在各方会导致语义不一致、超卖、对账难。独立库存中心提供统一 API + 统一账本。

第二步:挑战。统一语义、多场景扣减、一致性、对账、回补、多机房。

第三步:架构。库存中心:对外提供 tryDeduct(预扣)、confirm(实扣)、cancel(回补);②存储:DB 为权威账本(流水表+库存表),Redis 为加速层(缓存当前库存);③扣减:DB 条件更新(行锁+谓词)为最终裁判,Redis 预扣为加速;④流水:每笔扣减写流水(业务单号+类型+前后值),可审计;⑤对账:定时比对 Redis 与 DB、流水与库存,差异告警+自动/人工回补。

第四步:选型。MySQL(账本+流水,分库分表);Redis(缓存);CDC(Canal 同步流水到数仓);对账任务(定时)。

第五步:一致性。强一致:DB 条件扣减(单笔不可超卖);最终一致:Redis 与 DB 异步对齐 + 对账纠偏。允许少卖(可回补),禁止超卖。

第六步:高可用。库存中心多副本;DB 主从+半同步;Redis 集群;扣减失败有补偿。

第七步:优化。热点 SKU 分段(见第5题);流水归档;预扣超时自动 cancel(防长期占用)。

4.【架构设计】
上游:秒杀/普通/拼团/预售 ▼ 库存中心 API(tryDeduct / confirm / cancel) ▼ ├ Redis:加速层(预扣 + 缓存当前库存) └ MySQL:库存表(权威) + 库存流水表(审计) ▼ Canal(Binlog)→ 数仓 ▼ 对账任务(Redis vs DB;流水和 = 初始 - 已扣 + 已回补) └ 差异 → 告警 + 自动/人工回补
5.【技术方案深度解析】

为什么独立?库存是跨业务的核心状态,分散维护必然出现「A 扣了 B 不知」导致超卖。中心化提供统一语义、统一账本、统一对账,是防超卖的架构基石。

扣减语义(TCC 思想):tryDeduct 预占(Redis+DB 冻结)、confirm 实扣(业务成功提交)、cancel 回补(业务失败/超时释放)。三段式让「占用」与「消耗」分离,杜绝占而不用/用而不占。

强一致 vs 最终一致:单笔扣减必须强一致(DB 条件更新保证不超卖);Redis 与 DB 同步、跨机房同步允许最终一致(秒级),靠对账兜底。绝不能让「最终一致」出现在单笔扣减的裁判环节。

对账:恒等式:当前库存 = 初始库存 - Σ实扣 + Σ回补。核对:①Redis 缓存 vs DB 库存;②流水汇总 vs 库存表;③每日全量对账 + 实时差异告警。差异来源:消息丢失、扣减失败未回补、并发边界。

少卖 vs 超卖处理:少卖(Redis 预扣成功但 DB 失败)→ 回补 Redis,重新可卖;超卖(绝不允许)→ 发生即告警 + 冻结 + 人工赔付决策。架构上通过「DB 条件扣减为唯一裁判」从根上避免。

6.【关键技术点】
库存中心TCC 扣减语义DB 条件扣减(裁判)库存流水审计Redis 加速层对账恒等式回补预扣超时cancel
7.【Java 实现示例】
// 库存中心:DB 条件扣减(强一致裁判)+ 流水
@Transactional(rollbackFor = Exception.class)
public DeductResult confirm(Long skuId, int num, String bizNo) {
    int n = stockMapper.deduct(skuId, num);  // WHERE stock >= num
    if (n == 0) return DeductResult.fail("no stock");
    flowMapper.insert(new StockFlow(skuId, bizNo, "CONFIRM", -num));
    redis.opsForValue().decrement(stockKey(skuId), num);  // 异步对齐缓存
    return DeductResult.ok();
}
// 预扣超时自动回补(定时任务)
@Scheduled(fixedDelay = 30_000)
public void expirePreDeduct() {
    List<PreDeduct> list = preMapper.listExpired(5, TimeUnit.MINUTES);
    list.forEach(p -> { stockMapper.release(p.skuId(), p.num()); flowMapper.insert(cancel(p)); });
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 库存散落各业务系统——超卖/对账难。正确:中心化。
  • ❌ 只 Redis 扣减不落账本——无法审计/丢失。正确:DB 裁判+流水。
  • ❌ 无对账——差异累积。正确:恒等式对账+回补。
  • ❌ 预扣无超时——库存长期占用。正确:超时 cancel。
10.【架构师评分标准】
初级 0~40
不知库存中心价值;无账本/对账。
中级 40~60
说 Redis+DB,但不懂语义统一与对账。
高级 60~80
TCC 语义+DB 裁判+流水+对账+回补,给恒等式。
架构师 80~100
再加:①强一致 vs 最终一致边界清晰;②多机房单元化;③预扣超时治理;④流水归档与成本;⑤可观测(对账差异率/超卖件数=0)。

第 10 题:性能目标治理——P99、容量模型与成本优化 治理

1.【真实企业业务场景】
CTO 要求:「核心接口 P99 < 200ms、可用性 99.99%、大促成本不增 30%」。团队有搜索/交易/支付/物流 4 大域,云资源月费 800 万。要求建立性能目标体系、容量模型与成本优化机制,并能量化回答「加机器还是优化代码」。
2.【面试官问题】
    1. P99 和平均 RT 有什么区别,为什么看 P99?2. 容量模型怎么建?3. 「加机器 vs 优化代码」怎么决策?4. 成本怎么优化(不无脑堆)?5. 可用性 99.99% 意味着什么、怎么达成?6. 性能目标怎么落地到研发流程?
3.【候选人的标准回答】

第一步:分析。性能治理 = 目标可量化 + 容量可预测 + 成本可控 + 可用性可保障。平均 RT 掩盖长尾,P99/P999 才反映「最差用户体验」与系统风险。

第二步:挑战。长尾延迟、容量预测、成本与性能权衡、可用性量化、目标落地。

第三步:体系。指标:QPS/RT(P50/P99/P999)/错误率/饱和度,设 SLO;②容量模型:压测得单实例拐点 → 算实例数 + 余量;③决策矩阵:扩展性瓶颈(加机器)vs 算法/锁/IO 瓶颈(优化代码);④成本:弹性扩容、冷热分离、缓存命中率、资源超卖(K8s);⑤可用性:多副本/多 AZ/自动故障转移+降级;⑥流程:性能门禁(CI 压测)、大促前评审、复盘。

第四步:选型。Prometheus/Grafana(指标);JMeter/全链路压测(拐点);K8s HPA(弹性);混沌工程(可用性验证)。

第五步:一致性。目标一致:SLO 对齐业务(如支付 P99 200ms);成本与可用性冲突时按业务优先级取舍。

第六步:高可用。SLO 驱动冗余;故障演练;自动止损。

第七步:优化。先定位瓶颈(火焰图/链路追踪)再动手;优化 > 扩容(长期成本);扩容用于真水平扩展。

4.【架构设计】
业务 SLO(P99/可用/成本) ▼ 容量模型(拐点 QPS × 余量 → 实例数) ▼ 研发流程 ├ CI 性能门禁(压测不达标不发布) ├ 链路追踪定位瓶颈(SkyWalking) ▼ 决策:优化代码(算法/锁/IO) vs 扩容(水平扩展) ▼ 弹性 + 成本治理(HPA/冷热/超卖) ▼ 可用性保障(多 AZ/降级/演练)+ 复盘
5.【技术方案深度解析】

为什么看 P99 而非平均?平均 RT 100ms 可能掩盖「1% 请求 2s」——这些长尾正是雪崩前兆与最差体验。P99/P999 反映尾部,是容量与稳定性的真信号。优化目标应「压平长尾」(连接池、锁竞争、GC、慢依赖)。

容量模型:实例数 = ceil(峰值QPS × 安全系数 / 单实例拐点QPS),拐点 = RT 开始陡升的 QPS(通常取 P99=200ms 处)。各组件分别计算取最大,加 30% 余量应对突发。

加机器 vs 优化代码(决策矩阵):扩容解决:纯 CPU/连接/带宽瓶颈,且可水平扩展(无状态服务、只读);②优化解决:N+1 查询、慢 SQL、锁竞争、同步阻塞、大对象 GC、不必要的远程调用。原则:优化优先于扩容(扩容是线性成本且掩盖问题,优化降长期成本)。

成本优化:弹性(HPA/定时扩容,大促后缩容);冷热分离(热数据 Redis/SSD,冷数据对象存储/归档);提升缓存命中率(减少回源);K8s 资源超卖与调度优化;多云/竞价实例跑离线。目标:核心域保性能,非核心域压成本。

99.99% 可用性:年停机 ≈ 52 分钟。需多副本(无单点)、多 AZ、自动故障转移、降级预案、快速发布回滚。每个 9 都是架构投入:单副本 99% → 双副本 99.9% → 多 AZ+自动 99.99%。

6.【关键技术点】
P99/P999 长尾容量模型性能门禁(CI)链路追踪优化vs扩容决策弹性(HPA)冷热分离SLO/可用性
7.【Java 实现示例】
// CI 性能门禁:压测结果不满足 SLO 拒绝发布(伪代码)
public void perfGate(LoadTestResult r, SLO slo) {
    if (r.p99() > slo.p99Ms() || r.errorRate() > slo.errorRate()
        || r.throughput() < slo.minQps()) {
        fail("性能门禁未通过:P99=%sms 阈值=%sms", r.p99(), slo.p99Ms());
    }
}
// 瓶颈定位:分布式链路埋点(Micrometer + SkyWalking)
@Timed(value = "order.create", percentiles = {0.5, 0.99, 0.999}) // P50/P99/P999
public OrderVO create(OrderCmd cmd) { ... }
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 只看平均 RT——掩盖长尾雪崩。正确:看 P99/P999。
  • ❌ 性能差就加机器——掩盖问题+成本爆炸。正确:先定位优化。
  • ❌ 可用性拍脑袋 99.99%——无架构支撑。正确:多副本+多AZ+演练。
  • ❌ 成本无脑堆缓存——可能升延迟。正确:以 SLO 约束优化。
10.【架构师评分标准】
初级 0~40
只看平均 RT;无容量模型;加机器万能。
中级 40~60
知 P99 和加机器,但无决策矩阵与成本。
高级 60~80
容量模型+P99+优化vs扩容+成本优化+可用性量化。
架构师 80~100
再加:①长尾根因与压平策略;②性能门禁融入研发流程;③成本与 SLO 约束的平衡;④可用性每 9 的架构代价;⑤可观测(SLO 大盘/复盘机制)。

第 11 题:缓存预热与缓存回源风暴治理 缓存

1.【真实企业业务场景】
大促零点开门,首页坑位、爆款详情、会场配置等约 200 万个热点 Key 需在大促前就绪。历史教训:一次未预热,开门瞬间 200 万请求同时穿透到 DB,连接池被打满、DB CPU 100%,故障 6 分钟。要求:开门前缓存命中率 ≥ 99.5%,DB 回源 QPS ≤ 5000。
2.【面试官问题】
    1. 预热怎么做才不把 DB 打挂?2. 回源风暴(缓存集中过期)怎么防?3. 预热数据很多、单次拉取慢怎么办?4. 缓存和 DB 数据不一致怎么处理?5. 预热失败如何降级?
3.【候选人的标准回答】

第一步:分析。预热本质是「把读压力从开门瞬间平移到大促前」,并把回源从「并发」变「可控串行/分批」。

第二步:挑战。批量回源打 DB;Key 集中过期;预热耗时;一致性;失败兜底。

第三步:架构。分批 + 限速预热:定时任务按 SKU 分批(每批 1 万)多线程拉 DB 写入 Redis,限速 QPS ≤ 3000;②错峰过期:TTL 基础值 + 随机抖动(如 30min±5min),避免集体失效;③回源合并:singleflight/分布式锁,同一 Key 只回源一次;④多级缓存:本地 Caffeine 扛本地重复读。

第四步:选型。Redis(集群)+ Caffeine(本地)+ XXL-JOB(分批预热)+ singleflight(合并)。

第五步:一致性。预热数据与 DB 源一致即可;更新走「先更 DB 再删缓存(Cache-Aside)+ 延迟双删」。

第六步:高可用。预热任务可重试、可断点续跑;DB 限速保护;部分 Key 失败不影响其他。

第七步:优化。预热数据压缩(如 protobuf);按访问热度分级预热(只预热 Top 1% 爆款,长尾自然回源)。

4.【架构设计】
DB(只读副本) → 预热Job(分批限速) → Redis集群 ↑ 开门流量: 用户 → CDN → Gateway → 本地Caffeine → Redis → (回源合并singleflight) → DB 防集体失效: TTL = base + randomJitter
5.【技术方案深度解析】

为什么错峰过期?统一 TTL 会在同一时刻集体失效,引发回源风暴。为什么 singleflight?同一 Key 并发回源只放一个,其余复用,DB 压力降 N 倍。为什么本地缓存?扛实例内重复读,进一步降 Redis 压力。替代方案:「提前刷新」——TTL 到期前异步续期(看门狗式),但实现更复杂;错峰 TTL 更通用。

6.【关键技术点】
缓存预热错峰TTLsingleflightCache-AsideCaffeine分批限速
7.【Java 实现示例】
// 回源合并:Guava/自定义 singleflight,同一 key 仅回源一次
public <T> T loadWithMerge(String key, Function<String, T> loader) {
    return merges.computeIfAbsent(key, k -> {
        T v = loader.apply(k);
        return v; // 加载完成即移除(用 ConcurrentHashMap + 弱引用或主动清)
    });
}
// 分批预热(XXL-JOB 分片):每实例预热一段 SKU,限速
public void preheat(List<Long> skuIds) {
    skuIds.stream().limit(10000).forEach(sku -> {
        Item it = itemMapper.selectById(sku);          // 走只读从库
        redis.set(skuKey(sku), JSON.toJSONString(it),
            Duration.ofMinutes(30 + ThreadLocalRandom.current().nextInt(10))); // 错峰 TTL
        RateLimiter.get(3000).acquire();               // 全局限速 3000/s
    });
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 统一 TTL 半夜过期——开门集体回源。正确:错峰抖动。
  • ❌ 开门前一把梭全量拉 DB——打挂。正确:分批限速。
  • ❌ 不合并回源——N 倍压力。正确:singleflight。
10.【架构师评分标准】
初级 0~40
知预热但无限速/错峰,会打挂 DB。
中级 40~60
分批预热,不懂回源合并与错峰。
高级 60~80
分批限速+singleflight+错峰TTL+多级缓存+降级。
架构师 80~100
再加:①热点分级预热策略;②预热可观测(命中率/回源QPS);③断点续跑与失败隔离;④与容量模型联动。

第 12 题:高并发下单排队与异步化架构 削峰

1.【真实企业业务场景】
某票务平台演唱会开票:80 万人抢 5 万张票,瞬间峰值 QPS 40 万,但「生成订单 + 锁座 + 支付」全链路 DB 仅能扛 8000 TPS。要求:不让 40 万 QPS 直接冲垮交易系统,且用户 1 秒内得到「排队中/已抢到」反馈,最终 5 万张票不超卖、不漏发。
2.【面试官问题】
    1. 同步下单扛不住,怎么异步化?2. 排队怎么实现、用户怎么感知进度?3. 异步后如何保证不超卖、不重复?4. 排队超时/消息积压怎么办?5. 用户一直「排队中」体验怎么兜底?
3.【候选人的标准回答】

第一步:分析。下单是「重操作」,必须异步削峰:把 40 万 QPS 的「请求」与 8000 TPS 的「处理」解耦。

第二步:挑战。同步直写崩库;用户需即时反馈;超卖/重复;积压;超时。

第三步:架构。网关层预校验(登录/限购/风控)后发 MQ;②Redis Lua 原子占座(扣库存+占座+防重),成功才进队;③RocketMQ 顺序消费(按场次分区)匀速落库生成订单;④WebSocket/轮询 推送排队进度;⑤定时回滚占座未支付。

第四步:选型。RocketMQ(削峰/顺序/重试/死信)+ Redis(占座)+ WebSocket(进度)。

第五步:一致性。占座成功=可下单;DB 条件扣减为最终裁判;支付超时占座回滚(库存回补)。

第六步:高可用。MQ 堆积时消费扩容;死信队列兜底失败单;占座回滚补偿。

第七步:优化。本地缓存售罄标记;消费并行度按分区;热点场次独立队列隔离。

4.【架构设计】
用户 → Gateway(预校验+风控) → Redis Lua占座 → RocketMQ(场次分区) ↓顺序消费 订单服务(条件扣减) → MySQL ↓ WebSocket 推送「已抢到/排队中」
5.【技术方案深度解析】

为什么异步?请求流量与处理能力解耦,40 万 QPS 先收下,按 DB 能力匀速消费。为什么占座用 Lua?原子保证「扣库存+占座+防重」一步完成,不超卖。为什么要顺序消费?同场次顺序处理避免并发导致状态错乱;按场次分区保证吞吐。替代:同步直写简单但必崩;纯 Redis 扣减不持久化会丢。

6.【关键技术点】
异步削峰Redis LuaRocketMQ顺序消费占座回滚WebSocket
7.【Java 实现示例】
// 占座 Lua:库存-1、占座集合加入、重复返回-2
private static final String OCCUPY = """
    if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then return -2 end
    local s = tonumber(redis.call('GET', KEYS[1]))
    if not s or s <= 0 then return -1 end
    redis.call('DECR', KEYS[1]); redis.call('SADD', KEYS[2], ARGV[1]);
    return 1""";
// 消费:条件扣减生成订单(最终裁判)
@RocketMQMessageListener(topic = "ticket_order", consumeThreadMax = 32)
public void on(OrderMsg m) {
    int n = orderMapper.createIfSeat(m.userId(), m.showId()); // 唯一索引防重+库存条件
    if (n == 1) push(m.userId(), "已抢到");
}
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 同步直写——40 万 QPS 必崩。正确:异步削峰。
  • ❌ 占座用 select+update 非原子——超卖。正确:Lua。
  • ❌ 不回滚占座——库存被永久占用。正确:超时补偿。
10.【架构师评分标准】
初级 0~40
同步下单,无削峰概念。
中级 40~60
用 MQ,但占座非原子、无回滚。
高级 60~80
Lua占座+顺序消费+回滚+进度推送+死信。
架构师 80~100
再加:①请求/处理解耦的容量模型;②补单与对账;③分区隔离;④端到端可观测。

第 13 题:大促系统隔离与降级策略 稳定性

1.【真实企业业务场景】
大促期间秒杀域峰值流量是日常 50 倍,曾因秒杀域线程池打满拖垮「购物车」「订单查询」等核心域,全站不可用 20 分钟。要求:实现域级物理隔离,任一域故障不扩散;并定义清晰的降级开关,核心链路保命、非核心可丢。
2.【面试官问题】
    1. 怎么做到一个域挂了不影响其他域?2. 线程池/连接池隔离怎么设计?3. 降级开关怎么设计、怎么快速生效?4. 哪些该降级、哪些绝不能降级?5. 熔断和降级区别?
3.【候选人的标准回答】

第一步:分析。稳定性 = 隔离(不扩散)+ 降级(保核心)+ 熔断(快速失败)

第二步:挑战。故障扩散;资源争抢;降级误伤核心;开关生效慢。

第三步:架构。物理隔离:秒杀域独立集群/独立 K8s 命名空间/独立 DB 连接池;②线程池隔离:每个下游用独立线程池(舱壁模式),避免 A 慢拖 B;③熔断:Resilience4j/Sentinel 错误率超阈值断开,走 fallback;④降级开关:配置中心实时推送(如「推荐位→静态兜底」「评论→不展示」)。

第四步:选型。Sentinel(流控/熔断/降级)+ Resilience4j(舱壁)+ Nacos(开关)+ 独立资源池。

第五步:一致性。降级不破坏核心数据一致(订单/支付不降级);非核心可最终补偿。

第六步:高可用。隔离防扩散;熔断防雪崩;降级保核心 SLA。

第七步:优化。按业务优先级划分「保命/重要/可丢」三级,大促前演练降级。

4.【架构设计】
独立集群/Namespace: [秒杀域] [交易域] [查询域] [推荐域] 各域: 独立线程池 + 独立DB连接池 + 独立MQ消费组 Gateway: 熔断(Sentinel) → 降级开关(Nacos实时) → 核心保命/非核心兜底
5.【技术方案深度解析】

为什么物理隔离?共享资源池一损俱损。独立池把故障边界锁在域内。为什么舱壁模式?下游 A 慢只耗尽 A 的池,B 不受影响。熔断 vs 降级:熔断是「检测到下游不行就快速失败」,降级是「主动关掉非核心功能」。开关设计:必须配置中心实时推送 + 灰度生效,避免重启。

6.【关键技术点】
舱壁模式熔断降级开关SentinelResilience4j资源隔离
7.【Java 实现示例】
// 舱壁:每个下游独立线程池
ThreadPoolBulkhead seckillBulk = Bulkhead.of("seckill",
    BulkheadConfig.custom().maxConcurrentCalls(200).maxWaitDuration(Duration.ofMillis(50)).build());
Supplier<String> guarded = Bulkhead.decorateSupplier(seckillBulk, () -> seckillClient.buy());
// 熔断 + 降级
CircuitBreaker cb = CircuitBreaker.of("rec", CircuitBreakerConfig.ofDefaults());
Supplier<List> recs = CircuitBreaker.decorateSupplier(cb,
    () -> recommendClient.list(uid)); // 熔断后 fallback 返回空/静态
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 全站共用一个线程池——一损俱损。正确:舱壁隔离。
  • ❌ 降级靠改代码重启——生效慢。正确:配置中心实时开关。
  • ❌ 核心链路也降级——资损。正确:核心保命。
10.【架构师评分标准】
初级 0~40
无隔离,故障全扩散。
中级 40~60
知熔断,但无资源隔离与降级清单。
高级 60~80
舱壁+熔断+实时降级开关+核心分级。
架构师 80~100
再加:①物理/逻辑隔离组合;②降级演练与回滚;③成本与弹性平衡;④故障域收敛指标。

第 14 题:秒杀场景的风控与防刷体系 安全

1.【真实企业业务场景】
黄牛用 10 万脚本账号 + 代理 IP 池抢茅台,正常人抢不到;同时「虚假注册-领券-套现」黑产链路月损 200 万。要求:在不误杀正常用户前提下,拦截 > 99% 机器流量,且风控判断 RT < 20ms 不拖慢下单。
2.【面试官问题】
    1. 怎么区分人和机器?2. 风控放哪里、RT 怎么压住?3. 规则引擎和模型怎么配合?4. 误杀真人怎么办?5. 设备指纹怎么防伪造?
3.【候选人的标准回答】

第一步:分析。风控是「多层打分 + 实时决策」,不是单点规则。

第二步:挑战。识别机器、低延迟、误杀、对抗升级。

第三步:架构。网关层:验证码/滑块(高价值动作触发)、IP 频控;②设备指纹:采集 UA/Canvas/行为生成设备 ID,Redis 存风险分;③实时规则:用户维度频控(同一 uid/IP 短时多次);④模型:特征入实时计算(Flink),异常评分;⑤决策:分数→放行/挑战/拦截;⑥灰度+申诉兜底。

第四步:选型。Redis(频控/指纹)+ Flink(实时特征)+ 规则引擎(自研/Drools)+ 验证码服务。

第五步:一致性。风控只做决策不写业务数据;决策结果异步入离线库用于迭代模型。

第六步:高可用。风控自身故障「fail-open 放行但有监控」或 fail-close(高价值动作 fail-close);多级降级。

第七步:优化。本地缓存设备分;批量特征计算;RT 预算 20ms 内(只查缓存+轻规则)。

4.【架构设计】
请求 → Gateway(频控/验证码) → 设备指纹(Redis) → 规则引擎(实时特征/Flink) → 决策(放行/挑战/拦截) → 业务 离线: 决策日志 → 模型训练 → 规则迭代
5.【技术方案深度解析】

为什么放网关?越早拦截越省资源。但重模型放网关会拖 RT,所以用「网关轻规则 + 异步重模型」。规则 vs 模型:规则快可解释(拦截明显作弊),模型抓隐蔽对抗(需特征工程)。fail-open 风险:放行会被刷,故高价值动作 fail-close、低价值 fail-open。

6.【关键技术点】
设备指纹频控规则引擎Flink实时特征fail-open验证码
7.【Java 实现示例】
// 用户维度频控(Redis 滑动窗口,RT 极低)
public RiskLevel judge(long uid, String ip) {
    long cnt = redis.zcount(slideKey(uid), now - 60_000, now);
    if (cnt > 30) return BLOCK;            // 1分钟>30次 拦截
    if (cnt > 10) return CHALLENGE;       // 触发验证码
    return PASS;
}
// 设备指纹:行为+环境生成稳定 ID(对抗伪造靠多因子交叉)
String devId = Hashing.sha256().hashString(ua + canvas + touch + ipSeg).toString();
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 重模型放同步链路——RT 爆。正确:异步打分+缓存。
  • ❌ 一刀切拦截——误杀真人。正确:分级挑战。
  • ❌ 只靠 IP——代理池绕过。正确:设备指纹+行为。
10.【架构师评分标准】
初级 0~40
只知验证码,无分层。
中级 40~60
频控+规则,不懂模型与延迟预算。
高级 60~80
多层+设备指纹+实时特征+分级决策+fail策略。
架构师 80~100
再加:①对抗升级闭环(日志→模型→规则);②误杀率/拦截率度量;③高低价值 fail 分级;④隐私合规。

第 15 题:库存预热与防刷结合的流量整形 综合

1.【真实企业业务场景】
综合前两题:大促秒杀需同时解决「缓存预热 + 库存防超卖 + 防刷 + 削峰」四件事,且要在开门前 30 分钟自动完成预热、开门瞬间 50 万 QPS 平稳收敛到 1 万成功单。要求给出一套端到端流量整形流水线
2.【面试官问题】
    1. 四个目标怎么串成一条流水线?2. 预热时机与容量怎么定?3. 各层限流阈值怎么算?4. 全链路如何观测?5. 一键大促切换怎么做?
3.【候选人的标准回答】

第一步:分析。把单点方案编排为「预热→拦截→占座→异步→防护」流水线。

第二步:挑战。四目标耦合;阈值联动;可观测;一键切换。

第三步:架构。①开门前 30min:分批预热库存/商品到 Redis(错峰 TTL);②边界:CDN/WAF 拦 40% 异常;③网关:用户/接口限流 + 风控分级;④Redis Lua 占座(原子);⑤MQ 削峰异步落库;⑥全链路:SkyWalking + Prometheus + 大促大盘。

第四步:选型。Redis(预热/占座)+ RocketMQ(削峰)+ Sentinel(限流)+ SkyWalking(追踪)+ Nacos(一键开关)。

第五步:一致性。占座=可下单;DB 条件扣减裁判;对账回补。

第六步:高可用。各层独立降级;Redis/DB 故障降级路径;多副本。

第七步:优化。阈值由压测容量模型推导;本地售罄标记零 Redis 调用。

4.【架构设计】
[T-30min] 预热Job → Redis(库存/商品,错峰TTL) [开门] 用户→CDN/WAF→Gateway(限流+风控)→Redis Lua占座→MQ→订单→DB 每层: 降级开关 + 指标上报 → 大促大盘
5.【技术方案深度解析】

为什么编排而非堆组件?各层阈值基于下游容量联动:DB 2 万 TPS → MQ 消费 2 万 → 占座放行 ~1.2 万 → 网关限流 5 万 → WAF 拦异常。一键切换:Nacos 发布「大促模式」配置,自动开启限流/预热/降级策略组。

6.【关键技术点】
流量整形预热编排阈值联动一键切换大促大盘降级组
7.【Java 实现示例】
// 一键大促:Nacos 监听配置切换策略组
@NacosConfigListener(dataId = "seckill-mode")
public void onMode(String mode) {
    boolean big = "PROMO".equals(mode);
    sentinelRules.reload(big ? PROMO_RULES : NORMAL_RULES);
    preheatJob.setEnabled(big);
    switchGroup.setEnabled(big);
}
// 阈值联动:下游容量推导(示例)
// DB_TPS(2w) → 消费(2w) → 占座放行(1.2w) → 网关(5w) → WAF(异常拦截)
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 各层阈值独立拍脑袋——链路不匹配。正确:容量联动。
  • ❌ 大促靠手动改配置——易错慢。正确:一键策略组。
  • ❌ 无大促大盘——黑盒。正确:全链路可观测。
10.【架构师评分标准】
初级 0~40
零散方案,无编排。
中级 40~60
多组件但阈值孤立。
高级 60~80
流水线+联动阈值+一键切换+大盘。
架构师 80~100
再加:①容量模型驱动全链;②故障域与降级组;③演练与复盘机制;④成本与性能平衡。