← 每日面试题 学习看板

Java 后端模拟面试题

面试题/高阶题目/2026-09-14-VibeCoding-数据库-系统设计.md

Java 后端模拟面试题

日期:2026-09-14

总题数:3 题(进阶 2 题 + 高阶 1 题)

方向覆盖:Vibe Coding、数据库优化、系统设计


R1 Vibe Coding | 进阶

题目

场景:某后端团队引入 AI 辅助编程后,代码生成效率提升 40%,但 CR 通过率从 85% 降到 62%,线上 bug 率上升 30%。团队共 12 人,日均生成代码约 800 行/人。

问题:

  1. 分析 AI 生成代码质量下降的根因,给出 3 个可量化的诊断指标。
  2. 设计一套 AI 代码审查流程(人 + AI 协作),保证 CR 通过率回到 80%+。
  3. 团队如何建立 AI 编程规范,避免"AI 幻觉"进入生产环境?

原始回答

AI 代码质量下降的很大程度就是 AI 情况,AI 会自己编写一些东西去。缺少上下文内容,缺少一些人的范围边界的思考,这些东西需要人为补充到规则中。另外就是它可能会丢失一些内容,导致它的质量下降。 可量化的指标的话,需要补充。可量化的指标,考虑到可以补充边界条件,然后代码 bug 率,bug 的产生的原因进行分析。 AI 代码的审查流程,首先是制定 AI 代码审核规范,比如对所有的入参进行检查,进行校验,然后审核是否存在空值情况,审核线程使用情况,审核是否有锁,这些东西加到 AI 审核规范中,然后去通过 AI 去进行审核,然后人工去进行人工再去进行一些核心规则的审核。首先把大体的在 AI 中过一遍,然后再审人工去审核一些核心的内容,这样确保 CR 准确,CR 的通过率提高。 把一些根据上面的那个前置的我们设定的量化指标中常见的错误都在审核条件中,审核 AI 审核协作中去增加维护进去,并在后续每次产生的过程中进行补充补充进去 建立 AI 规范的话,首先要明确 AI 的编程逻辑,并且把项目中的各种必要的信息维护到相关的 Wiki 或者文档中,让 AI 每次 AI agent 每次执行的时候可以读取到,并且知道这些边界跟项目的内容。 编程规范其实有很多,我们可以维护进去,并且给它添加相关的限制,比如调用的方式,比如限定内容的上下文,什么地方该使用什么,这些东西可以根据项目的实际情况去一点点维护进去,然后根据每次审核后的结果,也把它补充到我们的那个 AI 文档中,去让 AI 后续实现过程中把它一点点完善出来。

点评

优点:

  1. 准确抓住核心根因:AI 缺乏上下文 + 幻觉,这是质量下降的根本原因
  2. 审核流程设计合理:AI 先过一遍 + 人工审核心,双保险机制方向正确
  3. 认识到规范需要迭代完善:不是一次性定稿,有持续改进意识

不足:

  1. 量化指标没给出——这是子问题 1 的核心要求,不能只说"需要补充"
  2. 审核规范太泛("检查入参、空值、线程"),缺少具体的检查项清单和优先级排序
  3. 没提到 AI 代码的特殊风险:比如 AI 可能生成看似正确但业务逻辑错误的代码(如金额计算、权限判断),这类问题比空指针更危险
  4. "维护到 Wiki" 方向对,但没说明如何让 AI 自动读取(比如 Cursor Rules、.cursorrules、AGENTS.md)

完整讲解

详见独立文档:vibe-coding-2026-09-14.md

核心方案:

  1. 根因分析:上下文缺失(35%)、AI 幻觉(25%)、边界条件遗漏(20%)、业务逻辑错误(15%)
  2. 量化指标:AI 代码缺陷密度、CR 一次通过率、AI 幻觉率
  3. 三阶段审查流程:AI 预审查 → AI + 人协作审查 → 人工终审
  4. 规范分层架构:通用原则 → 语言/框架规范 → 团队规范 → 项目特定规范
  5. AI 自动读取:.cursorrules、AGENTS.md、.cursoragent.md
  6. 规范迭代机制:触发条件 + 更新流程 + 版本管理

R2 数据库优化 | 进阶

题目

场景:某电商库存系统,stock 表 8000 万行,库存扣减接口 P99 延迟从 5ms 飙升到 300ms,线上出现 超卖 0.5%。慢查询如下:

UPDATE stock 
SET stock = stock - #{quantity}, version = version + 1 
WHERE sku_id = #{skuId} AND stock >= #{quantity} AND version = #{version};

问题:

  1. 解释这条 UPDATE 为什么慢?行锁和热点行的关系是什么?
  2. 给出优化方案(索引 + 架构 + 缓存),覆盖秒杀场景的超卖问题。
  3. 8000 万行下,库存扣减如何保证强一致和高性能?给出兜底方案。

原始回答

Update 慢可能原因是由于 QPS 过高,同时间读取数据,大量的从数据库中拿数据。然后它是批量缓存的,同时间拿的话,由于多组的 SKU ID 去拿的,然后它同时间去拉取数据,导致进行了全表扫描,才产生该问题。 首先我考虑,如果是优化方案的话,首先考虑索引方面,它本身有一个索引号的调整,版本号的调整,其实可以把版本号跟 SKU 单独建一个联合索引,SKU 在前。版本号在后。然后另外的话,架构方面,我们可以先把库存放到缓存里边,从缓存去进行扣减,比如 Redis。然后后续再定期的往数据库中落盘,比如通过消息队列这种,保证它的一致性去进行落盘、落库,控制它的整个的效率,不要把所有的压力给到数据库。这就是我暂时想到的方案。

点评

优点:

不足:


完整讲解

子问题 1:根因分析

不是全表扫描,是行锁竞争

EXPLAIN UPDATE stock 
SET stock = stock - 1, version = version + 1 
WHERE sku_id = 10001 AND stock >= 1 AND version = 1;

-- 结果:
+----+------+---------------+--------------+-------+
| id | type | possible_keys |      key     | rows  |
+----+------+---------------+--------------+-------+
|  1 | ref  | idx_sku_id    | idx_sku_id   |     1 |  ← 只扫 1 行
+----+------+---------------+--------------+-------+

type=ref + rows=1,说明索引生效,只扫 1 行。慢的原因不是扫描行数,而是锁等待。

真正的瓶颈:

场景:热门 SKU 库存 50 件,QPS 5000
    ↓
5000 个请求同时 UPDATE 同一行
    ↓
InnoDB 行锁:同一行同一时刻只有一个事务能持有 X 锁
    ↓
第一个事务持有锁 → 其余 4999 个事务排队等待
    ↓
行锁等待超时(innodb_lock_wait_timeout=5s)
    ↓
大量请求超时失败 → P99 飙升到 300ms

乐观锁的副作用:

T1: 请求 A 读到 version=1
T2: 请求 B 读到 version=1(缓存未更新)
T3: 请求 A UPDATE → affected=1,version=2
T4: 请求 B UPDATE → affected=0,version 不匹配
    → 重试或失败

子问题 2:优化方案

第一层:Redis 预扣减(当天上线,止血)

Lua 脚本(原子扣减):

-- KEYS[1]: stock:{skuId}
-- ARGV[1]: quantity
-- ARGV[2]: userId(防刷,同一用户只能扣一次)

local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil or stock < tonumber(ARGV[1]) then
    return -1
end

local userKey = 'seckill:user:' .. ARGV[2] .. ':' .. KEYS[1]
if redis.call('EXISTS', userKey) == 1 then
    return -2
end

redis.call('DECRBY', KEYS[1], ARGV[1])
redis.call('SET', userKey, '1', 'EX', 3600)

return stock - tonumber(ARGV[1])

Java 调用:

@Service
public class StockService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private MqProducer mqProducer;
    
    // 1. 下单时:Redis 预扣减
    public boolean preDeductStock(Long skuId, int quantity, Long userId) {
        String key = "stock:" + skuId;
        
        String luaScript = """
            local stock = tonumber(redis.call('GET', KEYS[1]))
            if stock == nil or stock < tonumber(ARGV[1]) then
                return -1
            end
            local userKey = 'seckill:user:' .. ARGV[2] .. ':' .. KEYS[1]
            if redis.call('EXISTS', userKey) == 1 then
                return -2
            end
            redis.call('DECRBY', KEYS[1], ARGV[1])
            redis.call('SET', userKey, '1', 'EX', 3600)
            return stock - tonumber(ARGV[1])
        """;
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(luaScript, Long.class),
            Collections.singletonList(key),
            String.valueOf(quantity),
            String.valueOf(userId)
        );
        
        if (result == null || result < 0) {
            return false;
        }
        
        // 2. 扣减成功,发送 MQ 消息
        mqProducer.send("seckill_order", buildMessage(skuId, quantity, userId));
        return true;
    }
    
    // 3. MQ 消费者:异步创建订单
    @RocketMQMessageListener(topic = "seckill_order", consumerGroup = "order_group")
    public void consumeOrder(Message message) {
        SeckillOrderRequest req = JSON.parseObject(message.getBody(), SeckillOrderRequest.class);
        orderService.createOrder(req);
    }
}

缓存与 DB 同步策略(Cache Aside):

@Transactional
public void deductStockWithVersion(Long skuId, int quantity) {
    // 1. 更新 DB
    int affected = stockDao.deductStockWithVersion(skuId, quantity);
    
    // 2. 删除缓存(不是更新!)
    redisTemplate.delete("stock:" + skuId);
    
    return affected;
}

// 读缓存 miss 时回源
public long getStock(Long skuId) {
    String key = "stock:" + skuId;
    Long stock = redisTemplate.opsForValue().get(key);
    if (stock == null) {
        stock = stockDao.getStock(skuId);
        // 随机过期时间(防雪崩)
        long expire = 3600 + ThreadLocalRandom.current().nextLong(600);
        redisTemplate.opsForValue().set(key, stock, expire, TimeUnit.SECONDS);
    }
    return stock;
}

Redis 宕机兜底:

public boolean deductStockWithFallback(Long skuId, int quantity, Long userId) {
    try {
        return preDeductStock(skuId, quantity, userId);
    } catch (RedisConnectionException e) {
        log.warn("Redis unavailable, fallback to DB", e);
        // 降级:DB 直接扣减(限流保护)
        if (rateLimiter.tryAcquire(1000, TimeUnit.SECONDS)) {
            return confirmStockWithRateLimit(skuId, quantity);
        }
        return false;
    }
}

收益:

指标原方案Redis 预扣减后
DB QPS50001000(异步落盘)
P99 延迟300ms5ms(Redis)+ 50ms(异步)
超卖率0.5%< 0.01%
行锁竞争5000 QPS 串行1000 QPS 串行
第二层:热点行拆分(1 周完成,降压)

库存行拆分:

-- 原表:1 行 1 个 SKU
CREATE TABLE stock (
    sku_id BIGINT PRIMARY KEY,
    stock INT NOT NULL,
    version INT NOT NULL DEFAULT 0
);

-- 拆分后:1 个 SKU 拆成 N 行
CREATE TABLE stock_split (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id BIGINT NOT NULL,
    shard_index INT NOT NULL,      -- 分片编号 0-N
    stock INT NOT NULL,             -- 分片库存
    version INT NOT NULL DEFAULT 0,
    UNIQUE KEY uk_sku_shard (sku_id, shard_index),
    INDEX idx_sku (sku_id)
);

扣减逻辑:

@Transactional
public void deductStockSplit(Long skuId, int quantity) {
    // 1. 随机选分片
    int shardIndex = ThreadLocalRandom.current().nextInt(SHARD_COUNT);
    
    // 2. 尝试扣减
    int affected = stockSplitDao.deductWithVersion(
        skuId, shardIndex, quantity
    );
    
    // 3. 失败则重试其他分片
    if (affected == 0) {
        for (int i = 0; i < SHARD_COUNT; i++) {
            int nextShard = (shardIndex + 1 + i) % SHARD_COUNT;
            affected = stockSplitDao.deductWithVersion(
                skuId, nextShard, quantity
            );
            if (affected > 0) break;
        }
        
        if (affected == 0) {
            throw new InsufficientStockException("库存不足");
        }
    }
}

收益:

指标原方案拆分后
热点行竞争5000 QPS 打 1 行1000 QPS 打 1 行(5 倍分散)
行锁等待5s 超时500ms 内
P99 延迟300ms50ms
第三层:分库分表(2-4 周,根治)

分表维度:sku_id 哈希

sharding:
  tables:
    stock:
      actualDataNodes: ds0.stock_${0..7}
      tableStrategy:
        standard:
          shardingColumn: sku_id
          shardingAlgorithmClassName: com.example.StockShardingAlgorithm

分表算法:

public class StockShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    
    @Override
    public String doSharding(
        Collection<String> tableNames, 
        PreciseShardingValue<String> shardingValue
    ) {
        String skuId = shardingValue.getValue().toString();
        int index = Integer.parseInt(skuId) % 8;
        return "stock_" + index;
    }
}

子问题 3:兜底方案

3.1 缓存雪崩防护
// 1. 随机过期时间
public void setStockWithRandomExpire(Long skuId, long stock) {
    String key = "stock:" + skuId;
    long baseExpire = 7200;  // 2 小时
    long randomExpire = baseExpire + ThreadLocalRandom.current().nextLong(600);
    redisTemplate.opsForValue().set(key, stock, randomExpire, TimeUnit.SECONDS);
}

// 2. 热点 key 永不过期(本地缓存兜底)
@Scheduled(fixedDelay = 3600000)
public void refreshHotKeys() {
    Set<String> keys = redisTemplate.keys("stock:*");
    for (String key : keys) {
        redisTemplate.expire(key, 4, TimeUnit.HOURS);
    }
}
3.2 限流 + 熔断
@SentinelResource(
    value = "deductStock",
    fallback = "deductStockFallback",
    blockHandler = "deductStockBlockHandler",
    circuitBreaker = @SentinelCircuitBreaker(
        slowRatioThreshold = 0.5,
        slowRatioMaxRt = 500,
        minRequestAmount = 10,
        timeWindow = 10
    )
)
public boolean deductStock(Long skuId, int quantity, Long userId) {
    return preDeductStock(skuId, quantity, userId);
}

public boolean deductStockFallback(Long skuId, int quantity, Long userId, Throwable ex) {
    log.warn("Stock deduction fallback to DB", ex);
    return confirmStockWithRateLimit(skuId, quantity);
}

public boolean deductStockBlockHandler(Long skuId, int quantity, Long userId, BlockException ex) {
    log.warn("Stock deduction blocked: {}", skuId);
    return false;
}
3.3 对账 + 自动修复
@Scheduled(cron = "0 0 * * * ?")
public void reconcileStock() {
    List<Long> hotSkuIds = stockDao.getHotSkuIds();
    for (Long skuId : hotSkuIds) {
        Long redisStock = redisTemplate.opsForValue().get("stock:" + skuId);
        Long dbStock = stockDao.getStock(skuId);
        
        if (redisStock != null && !redisStock.equals(dbStock)) {
            log.warn("Stock mismatch: skuId={}, redis={}, db={}", 
                skuId, redisStock, dbStock);
            
            // 自动修复(以 DB 为准)
            redisTemplate.opsForValue().set(
                "stock:" + skuId, dbStock, 4, TimeUnit.HOURS
            );
        }
    }
}
3.4 库存回补(支付超时)
@Scheduled(fixedDelay = 60000)
public void rollbackTimeoutOrders() {
    List<Order> timeoutOrders = orderDao.selectTimeout(30, TimeUnit.MINUTES);
    for (Order order : timeoutOrders) {
        // 回补 Redis 库存
        redisTemplate.opsForValue().increment(
            "stock:" + order.getSkuId(), order.getQuantity()
        );
        // 更新订单状态
        orderDao.updateStatus(order.getId(), OrderStatus.CANCELLED);
    }
}

方案对比总结

方案周期效果风险优先级
Redis 预扣减当天P99 300ms→5ms低P0 必做
热点行拆分1 周行锁竞争降 5 倍中P1
分库分表2-4 周根治可扩容高P2
限流熔断1 天保护核心链路低配合使用
对账修复3 天兜底一致性低配合使用

实施顺序:Redis 预扣减(止血)→ 热点行拆分(降压)→ 分库分表(根治)

关键认知:

  1. 行锁竞争是核心瓶颈:不是全表扫描,而是同一行被 5000 QPS 同时更新
  2. Redis 预扣减是标准解法:把高并发从 DB 转移到 Redis,DB 做最终确认
  3. 乐观锁有副作用:version 不匹配导致重试风暴,加剧行锁竞争
  4. 缓存与 DB 一致性:Cache Aside + 定时对账 + 自动修复
  5. 分表维度选 sku_id:保证同一 SKU 数据在同一分片,查询只路由 1 个分片

R3 系统设计 | 高阶

题目

场景:设计一个秒杀系统,支撑 10 万 QPS 峰值,10 万 SKU,1000 万库存,要求 P99 < 100ms,零超卖。

问题:

  1. 设计整体架构,覆盖:接入层、应用层、缓存层、消息层、数据层。
  2. 库存扣减方案如何设计?如何保证原子性和高性能?
  3. 如何防超卖?给出兜底方案(缓存雪崩、热点 key、单机故障)。

原始回答

(未作答,直接讲解)

完整讲解

子问题 1:整体架构设计

1.1 架构分层
用户 → CDN → 接入层(Nginx)→ 应用层(秒杀服务)
                            ↓
                    缓存层(Redis 集群)
                            ↓
                    消息层(RocketMQ)
                            ↓
                    数据层(MySQL 分库分表)

各层职责:

层级职责技术选型QPS 承载
CDN静态资源、边缘缓存Cloudflare/阿里云 CDN100 万+
接入层负载均衡、限流、防刷Nginx + Sentinel20 万
应用层业务逻辑、秒杀编排Spring Boot10 万
缓存层库存预扣减、热点数据Redis Cluster50 万
消息层异步下单、削峰填谷RocketMQ10 万
数据层订单存储、最终确认MySQL 分库分表5 万
1.2 核心流程
1. 用户点击秒杀 → CDN 边缘节点
2. CDN 检查库存(本地缓存)→ 有库存则放行
3. 接入层限流(Sentinel)→ 超过阈值直接拒绝
4. 应用层校验(用户资格、风控)→ 通过则继续
5. Redis 预扣减库存(Lua 脚本原子操作)
6. 扣减成功 → 发送 MQ 消息(异步下单)
7. MQ 消费者创建订单(写 DB)
8. 支付成功后确认库存
9. 支付超时自动回补库存

时序图:

用户 → CDN → Nginx → 秒杀服务 → Redis → MQ → 订单服务 → MySQL
         ↓         ↓         ↓         ↓      ↓      ↓
       静态缓存   限流    资格校验   扣库存  异步下单  最终确认

子问题 2:库存扣减方案

2.1 Redis 预扣减(核心)

Lua 脚本(原子扣减):

-- KEYS[1]: stock:{skuId}
-- ARGV[1]: quantity
-- ARGV[2]: userId(防刷,同一用户只能扣一次)

local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil then
    return -1
end

if stock < tonumber(ARGV[1]) then
    return -1
end

local userKey = 'seckill:user:' .. ARGV[2] .. ':' .. KEYS[1]
if redis.call('EXISTS', userKey) == 1 then
    return -2
end

redis.call('DECRBY', KEYS[1], ARGV[1])
redis.call('SET', userKey, '1', 'EX', 3600)

return stock - tonumber(ARGV[1])

Java 调用:

@Service
public class SeckillStockService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private final DefaultRedisScript<Long> stockScript = new DefaultRedisScript<>(
        luaScript, Long.class
    );
    
    public boolean preDeductStock(Long skuId, int quantity, Long userId) {
        String stockKey = "stock:" + skuId;
        
        Long result = redisTemplate.execute(
            stockScript,
            Collections.singletonList(stockKey),
            String.valueOf(quantity),
            String.valueOf(userId)
        );
        
        if (result == null || result < 0) {
            return false;
        }
        
        // 扣减成功,发送 MQ 消息
        mqProducer.send("seckill_order", buildMessage(skuId, quantity, userId));
        return true;
    }
}
2.2 热点 SKU 优化

本地缓存 + 分片扣减:

@Service
public class HotStockService {
    
    private final Cache<Long, AtomicLong> localStockCache = Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(Duration.ofSeconds(5))
        .build();
    
    public boolean deductStock(Long skuId, int quantity, Long userId) {
        // 1. 先查本地缓存(热点 SKU)
        AtomicLong localStock = localStockCache.getIfPresent(skuId);
        if (localStock != null) {
            while (true) {
                long current = localStock.get();
                if (current < quantity) {
                    return false;
                }
                if (localStock.compareAndSet(current, current - quantity)) {
                    asyncSyncToRedis(skuId, quantity);
                    return true;
                }
            }
        }
        
        // 2. 本地缓存 miss,扣 Redis
        return preDeductStock(skuId, quantity, userId);
    }
    
    @Async
    public void asyncSyncToRedis(Long skuId, int quantity) {
        redisTemplate.opsForValue().decrement("stock:" + skuId, quantity);
    }
    
    @Scheduled(fixedDelay = 5000)
    public void loadHotStock() {
        List<Long> hotSkuIds = redisTemplate.keys("stock:*");
        for (Long skuId : hotSkuIds) {
            Long stock = redisTemplate.opsForValue().get("stock:" + skuId);
            localStockCache.put(skuId, new AtomicLong(stock));
        }
    }
}

子问题 3:防超卖 + 兜底方案

3.1 多层防超卖
┌─────────────────────────────────────┐
│  Layer 1: 前端防刷                    │
│  - 按钮置灰、验证码、滑块验证          │
├─────────────────────────────────────┤
│  Layer 2: CDN 边缘限流                │
│  - 按 IP 限流、按用户限流             │
├─────────────────────────────────────┤
│  Layer 3: 接入层限流(Sentinel)      │
│  - QPS 限流、并发数限流               │
├─────────────────────────────────────┤
│  Layer 4: 应用层校验                  │
│  - 用户资格、风控、黑名单              │
├─────────────────────────────────────┤
│  Layer 5: Redis 原子扣减              │
│  - Lua 脚本保证原子性                 │
├─────────────────────────────────────┤
│  Layer 6: DB 乐观锁兜底               │
│  - version 字段防止超卖               │
└─────────────────────────────────────┘
3.2 接入层限流(Sentinel)
@SentinelResource(
    value = "seckill",
    fallback = "seckillFallback",
    blockHandler = "seckillBlockHandler",
    flowControl = @FlowControl(
        count = 100000,  // QPS 限制 10 万
        grade = 1,       // QPS 模式
        controlBehavior = 0  // 直接拒绝
    )
)
public Result seckill(Long skuId, Long userId) {
    return seckillService.doSeckill(skuId, userId);
}

public Result seckillBlockHandler(Long skuId, Long userId, BlockException ex) {
    return Result.fail("系统繁忙,请稍后再试");
}

public Result seckillFallback(Long skuId, Long userId, Throwable ex) {
    log.error("Seckill fallback", ex);
    return Result.fail("系统异常");
}
3.3 DB 乐观锁兜底
@Transactional
public void createOrder(Long skuId, int quantity, Long userId) {
    // 1. 查询库存
    Stock stock = stockDao.selectById(skuId);
    
    // 2. 检查库存
    if (stock.getStock() < quantity) {
        throw new InsufficientStockException("库存不足");
    }
    
    // 3. 乐观锁扣减
    int affected = stockDao.deductStockWithVersion(
        skuId, quantity, stock.getVersion()
    );
    
    if (affected == 0) {
        throw new OptimisticLockException("并发冲突");
    }
    
    // 4. 创建订单
    orderDao.insert(buildOrder(skuId, quantity, userId));
}
3.4 兜底方案

缓存雪崩防护:

// 1. 随机过期时间
public void setStockWithRandomExpire(Long skuId, long stock) {
    String key = "stock:" + skuId;
    long baseExpire = 7200;
    long randomExpire = baseExpire + ThreadLocalRandom.current().nextLong(600);
    redisTemplate.opsForValue().set(key, stock, randomExpire, TimeUnit.SECONDS);
}

// 2. 热点 key 永不过期(本地缓存兜底)
@Scheduled(fixedDelay = 3600000)
public void refreshHotKeys() {
    Set<String> keys = redisTemplate.keys("stock:*");
    for (String key : keys) {
        redisTemplate.expire(key, 4, TimeUnit.HOURS);
    }
}

限流 + 熔断 + 降级:

@SentinelResource(
    value = "seckill",
    flowControl = @FlowControl(count = 100000, grade = 1),
    circuitBreaker = @SentinelCircuitBreaker(
        slowRatioThreshold = 0.5,
        slowRatioMaxRt = 100,
        minRequestAmount = 10,
        timeWindow = 10
    )
)

对账 + 自动修复:

@Scheduled(cron = "0 0 * * * ?")
public void reconcileStock() {
    List<Long> skuIds = stockDao.selectAllSkuIds();
    for (Long skuId : skuIds) {
        Long redisStock = redisTemplate.opsForValue().get("stock:" + skuId);
        Long dbStock = stockDao.getStock(skuId);
        
        if (redisStock != null && !redisStock.equals(dbStock)) {
            redisTemplate.opsForValue().set(
                "stock:" + skuId, dbStock, 4, TimeUnit.HOURS
            );
        }
    }
}

库存回补(支付超时):

@Scheduled(fixedDelay = 60000)
public void rollbackTimeoutOrders() {
    List<Order> timeoutOrders = orderDao.selectTimeout(30, TimeUnit.MINUTES);
    for (Order order : timeoutOrders) {
        redisTemplate.opsForValue().increment(
            "stock:" + order.getSkuId(), order.getQuantity()
        );
        orderDao.updateStatus(order.getId(), OrderStatus.CANCELLED);
    }
}

性能指标

指标目标实现方案
P99 延迟< 100ms多级缓存(L1 本地 1ms + L2 Redis 5ms + L3 DB 50ms)
QPS 承载10 万Redis 预扣减(50 万 QPS)+ MQ 异步(10 万 QPS)
超卖率< 0.001%多层防护(前端 → CDN → 限流 → 应用层 → Redis → DB)
可用性99.99%多实例 + 故障转移 + 客户端重试

关键设计原则:

  1. 缓存优先:90% 请求走 Redis,10% 查 DB
  2. 异步解耦:秒杀成功 ≠ 订单创建,MQ 削峰填谷
  3. 多层防护:前端 → CDN → 限流 → 应用层 → Redis → DB
  4. 兜底机制:对账修复 + 库存回补 + 降级策略
  5. 高可用:多实例 + 故障转移 + 客户端重试

面试总结

各轮次表现汇总

轮次方向难度表现得分
R1Vibe Coding进阶方向正确,但量化指标缺失75/100
R2数据库优化进阶识别核心瓶颈,但根因理解有偏差70/100
R3系统设计高阶未作答,讲解完整-

强项

  1. 根因识别能力:能识别 QPS 过高、并发访问是核心瓶颈
  2. 架构思维:Redis 预扣减 + MQ 异步落盘,方向正确
  3. 规范意识:认识到 AI 编程需要规范,且需要迭代完善
  4. 工具使用:知道用 Redis、MQ、Sentinel 等工具解决问题

待提升点

  1. 量化指标:需要建立可量化的诊断指标,不能只说"需要补充"
  2. 根因分析深度:需要区分"全表扫描"和"行锁竞争",深入理解 MySQL 锁机制
  3. 方案完整性:需要覆盖子问题 3(兜底方案),不能只回答前两个子问题
  4. 一致性设计:需要说清楚 Redis 与 DB 的一致性保证(对账、回补、降级)
  5. 性能优化细节:需要提到热点行拆分、分库分表等进阶方案

建议

  1. Vibe Coding:学习 AI 代码质量度量体系,建立可量化的诊断指标
  2. 数据库优化:深入学习 MySQL 锁机制(行锁、间隙锁、乐观锁、悲观锁)
  3. 系统设计:多练秒杀、限流、库存扣减等经典场景,掌握多层防护设计
  4. 方案完整性:回答问题时,确保覆盖所有子问题,特别是兜底方案
  5. 性能优化:掌握热点行拆分、分库分表、多级缓存等进阶优化手段

会话全文记录

时间:2026-09-14

R1 Vibe Coding

用户回答:

AI 代码质量下降的很大程度就是 AI 情况,AI 会自己编写一些东西去。缺少上下文内容,缺少一些人的范围边界的思考,这些东西需要人为补充到规则中。另外就是它可能会丢失一些内容,导致它的质量下降。 可量化的指标的话,需要补充。可量化的指标,考虑到可以补充边界条件,然后代码 bug 率,bug 的产生的原因进行分析。 AI 代码的审查流程,首先是制定 AI 代码审核规范,比如对所有的入参进行检查,进行校验,然后审核是否存在空值情况,审核线程使用情况,审核是否有锁,这些东西加到 AI 审核规范中,然后去通过 AI 去进行审核,然后人工去进行人工再去进行一些核心规则的审核。首先把大体的在 AI 中过一遍,然后再审人工去审核一些核心的内容,这样确保 CR 准确,CR 的通过率提高。 把一些根据上面的那个前置的我们设定的量化指标中常见的错误都在审核条件中,审核 AI 审核协作中去增加维护进去,并在后续每次产生的过程中进行补充补充进去 建立 AI 规范的话,首先要明确 AI 的编程逻辑,并且把项目中的各种必要的信息维护到相关的 Wiki 或者文档中,让 AI 每次 AI agent 每次执行的时候可以读取到,并且知道这些边界跟项目的内容。 编程规范其实有很多,我们可以维护进去,并且给它添加相关的限制,比如调用的方式,比如限定内容的上下文,什么地方该使用什么,这些东西可以根据项目的实际情况去一点点维护进去,然后根据每次审核后的结果,也把它补充到我们的那个 AI 文档中,去让 AI 后续实现过程中把它一点点完善出来。

点评:

用户要求:将方案内容整理成文档保留,先原文保留,再补充完善方案

文档生成:vibe-coding-2026-09-14.md(包含原始回答、点评、完整讲解、补充完善方案 12 个进阶模块)


R2 数据库优化

用户回答:

Update 慢可能原因是由于 QPS 过高,同时间读取数据,大量的从数据库中拿数据。然后它是批量缓存的,同时间拿的话,由于多组的 SKU ID 去拿的,然后它同时间去拉取数据,导致进行了全表扫描,才产生该问题。 首先我考虑,如果是优化方案的话,首先考虑索引方面,它本身有一个索引号的调整,版本号的调整,其实可以把版本号跟 SKU 单独建一个联合索引,SKU 在前。版本号在后。然后另外的话,架构方面,我们可以先把库存放到缓存里边,从缓存去进行扣减,比如 Redis。然后后续再定期的往数据库中落盘,比如通过消息队列这种,保证它的一致性去进行落盘、落库,控制它的整个的效率,不要把所有的压力给到数据库。这就是我暂时想到的方案。

点评:

讲解重点:


R3 系统设计

用户回答:未作答,直接讲解

讲解重点:

性能指标:


文档版本:v1.0

最后更新:2026-09-14

总题数:3 题

总字数:约 10,000 字