写了9年代码,我靠这8道架构题拿下了P7 offer
面试官问:“你的系统日订单量从10万涨到1000万,架构怎么演进?” 这道题我准备了3个月,最终帮我拿下了P7。
大家好,我是卷毛。
去年年底我面试了几家大厂,最终拿到了P7的offer。回头看整个面试过程,架构设计题是最拉分的环节。我把面试中被问到的8道核心架构题整理出来,每道题都附上我的回答思路和面试官追问的方向。
这篇文章建议收藏,面试前拿出来复习。
题目一:高并发下单如何防止超卖?
场景描述
秒杀场景,1000件商品,10万人同时抢购,如何保证不超卖?
我的回答
三层防护:前端限流 → 分布式锁 → 数据库兜底
// 第一层:Redis预扣库存(原子操作)publicbooleandeductStock(StringitemId,StringuserId){Stringkey="stock:"+itemId;// Lua脚本保证原子性StringluaScript=""" if redis.call('get', KEYS[1]) then if tonumber(redis.call('get', KEYS[1])) > 0 then redis.call('decr', KEYS[1]) return 1 end end return 0 """;Longresult=redisTemplate.execute(newDefaultRedisScript<>(luaScript,Long.class),Collections.singletonList(key));if(result==null||result==0){returnfalse;// 库存不足}// 第二层:发送MQ异步创建订单orderMessageProducer.send(newOrderMessage(itemId,userId));returntrue;}// 第三层:数据库乐观锁兜底@TransactionalpublicvoidcreateOrder(StringitemId,StringuserId){// 乐观锁:WHERE stock > 0intaffected=jdbcTemplate.update("UPDATE item SET stock = stock - 1 WHERE id = ? AND stock > 0",itemId);if(affected==0){// Redis预扣了但DB没有了,补偿redisTemplate.opsForValue().increment("stock:"+itemId);thrownewSoldOutException("商品已售罄");}orderDao.insert(newOrder(itemId,userId));}面试官追问
Q:Redis挂了怎么办?
A:Redis Cluster高可用 + 本地缓存降级 + 数据库直接扛(限流降级)Q:如果用户扣了库存但不下单怎么办?
A:MQ设置延迟消息,15分钟未支付自动回滚库存Q:分布式锁用Redis还是ZooKeeper?
A:秒杀场景用Redis(性能优先),金融场景用ZooKeeper(一致性优先)
题目二:如何设计一个分布式锁?
场景描述
系统有多个节点,某个定时任务只能在一个节点执行,怎么实现?
我的回答
// 方案一:Redis分布式锁(Redisson)publicvoidexecuteTask(){RLocklock=redissonClient.getLock("scheduled:task:lock");try{// 尝试加锁,等待0秒(非阻塞),租约30秒if(lock.tryLock(0,30,TimeUnit.SECONDS)){doTask();}else{log.info("其他节点正在执行,跳过");}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}}// 方案二:数据库唯一索引(最简单可靠)@TransactionalpublicvoidexecuteTask(){try{// 唯一索引:(task_name, execute_date)taskLockDao.insert(newTaskLock("scheduled_task",LocalDate.now()));doTask();}catch(DuplicateKeyExceptione){log.info("其他节点已获取锁");}}三种方案对比
| 方案 | 性能 | 可靠性 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Redis(Redisson) | 高 | 中(需看门狗) | 中 | 高并发、允许偶尔失败 |
| ZooKeeper | 中 | 高 | 高 | 金融、强一致性要求 |
| 数据库唯一索引 | 低 | 高 | 低 | 低频任务、最可靠 |
面试官追问
Q:Redis锁的看门狗机制是什么?
A:Redisson的lock会启动一个后台线程,每隔1/3租约时间续期,防止业务没执行完锁就过期了Q:RedLock算法了解吗?
A:向N个独立Redis节点同时加锁,超过半数成功才算加锁成功。但Martin Kleppmann指出有安全问题,生产中我更倾向于用Redis Cluster + Redisson
题目三:百万级消息堆积怎么处理?
场景描述
消费者挂了一段时间,MQ堆积了百万条消息,重启后如何快速消费?
我的回答
// 核心思路:临时扩容消费者 + 批量消费 + 跳过堆积// 1. 临时增加消费者实例(K8s快速扩容)// 从3个Pod扩到20个Pod// 2. 批量消费模式@KafkaListener(topics="order-events",groupId="order-consumer",containerFactory="batchFactory")publicvoidbatchConsume(List<ConsumerRecord<String,String>>records){// 批量处理,减少网络往返List<Order>orders=records.stream().map(r->JSON.parseObject(r.value(),Order.class)).toList();orderService.batchInsert(orders);}// 3. 配置批量消费@BeanpublicConcurrentKafkaListenerContainerFactory<String,String>batchFactory(ConsumerFactory<String,String>consumerFactory){ConcurrentKafkaListenerContainerFactory<String,String>factory=newConcurrentKafkaListenerContainerFactory<>();factory.setConsumerFactory(consumerFactory);factory.setBatchListener(true);factory.getContainerProperties().setPollTimeout(3000);returnfactory;}面试官追问
Q:如果消息有时效性,过期消息怎么处理?
A:消费者端判断时间戳,过期消息直接丢弃 + 记录日志Q:如何避免堆积再次发生?
A:监控消费延迟、设置告警阈值、消费者优雅下线(处理完当前消息再退出)
题目四:如何设计接口幂等性?
场景描述
支付接口可能被重复调用(网络重试、用户双击),如何保证幂等?
我的回答
// 核心方案:唯一请求ID + 状态机@ServicepublicclassPaymentService{@AutowiredprivateRedisTemplate<String,String>redisTemplate;@AutowiredprivatePaymentDaopaymentDao;@TransactionalpublicPaymentResultpay(PaymentRequestrequest){StringidempotentKey="pay:"+request.getRequestId();// Step 1: Redis标记"处理中"BooleanfirstTime=redisTemplate.opsForValue().setIfAbsent(idempotentKey,"PROCESSING",30,TimeUnit.MINUTES);if(Boolean.FALSE.equals(firstTime)){// 不是第一次请求,查之前的结果Stringstatus=redisTemplate.opsForValue().get(idempotentKey);if("SUCCESS".equals(status)){// 返回之前的结果returnpaymentDao.findByRequestId(request.getRequestId()).toResult();}if("PROCESSING".equals(status)){thrownewConcurrentRequestException("请求处理中,请勿重复提交");}}// Step 2: 执行业务try{PaymentResultresult=doPayment(request);// Step 3: 更新状态为成功redisTemplate.opsForValue().set(idempotentKey,"SUCCESS",30,TimeUnit.MINUTES);returnresult;}catch(Exceptione){// 失败了删除标记,允许重试redisTemplate.delete(idempotentKey);throwe;}}}4种幂等方案
| 方案 | 实现方式 | 适用场景 |
|---|---|---|
| 唯一索引 | DB唯一约束 | 插入操作 |
| 乐观锁 | version字段更新 | 更新操作 |
| 状态机 | 状态流转校验 | 有状态的业务 |
| Token机制 | 预获取token,使用后失效 | 表单提交 |
题目五:系统从单机到千万级并发,架构怎么演进?
我的回答
阶段1:单体应用(日活1万) → Spring Boot单体 + MySQL单机 + Redis单机 阶段2:垂直拆分(日活10万) → 按业务拆分服务(用户、订单、商品) → 数据库读写分离 阶段3:微服务化(日活100万) → Spring Cloud微服务 → 分库分表(订单表按用户ID分16库×64表) → 消息队列异步解耦 → CDN + 分布式缓存 阶段4:中台化(日活1000万) → 业务中台 + 数据中台 → 异地多活(单元化部署) → 全链路压测 + 监控告警 每个阶段的核心原则: → 不要过度设计,当前阶段能撑住就行 → 但要预留扩展点(接口抽象、数据分片键设计) → 架构演进是连续的,不是一步到位的面试官追问
Q:分库分表后跨库join怎么做?
A:1)应用层组装 2)宽表冗余 3)ElasticSearch做查询侧 4)避免跨库join的设计Q:分库分表键怎么选?
A:选择查询频率最高的维度。订单系统选user_id,因为90%的查询是"查某用户的订单"
题目六:如何保证分布式事务的最终一致性?
我的回答
// 核心方案:本地消息表 + MQ可靠投递// Step 1: 业务操作 + 记录本地消息(同一个事务)@TransactionalpublicvoidcreateOrder(Orderorder){orderDao.insert(order);// 业务操作// 本地消息表(和业务表在同一个DB)messageDao.insert(newLocalMessage(UUID.randomUUID().toString(),"order-created",JSON.toJSONString(order),"PENDING"));}// Step 2: 定时扫描本地消息表,投递到MQ@Scheduled(fixedDelay=5000)publicvoidsendPendingMessages(){List<LocalMessage>messages=messageDao.findPending(100);for(LocalMessagemsg:messages){try{kafkaTemplate.send("order-events",msg.getContent());messageDao.markSent(msg.getId());}catch(Exceptione){// 下次继续重试log.error("发送失败",e);}}}// Step 3: 消费者幂等消费 + 回调确认@KafkaListener(topics="order-events")publicvoidconsume(Stringmessage){Orderorder=JSON.parseObject(message,Order.class);// 幂等检查if(inventoryService.isProcessed(order.getId())){return;}inventoryService.deduct(order);}面试官追问
Q:为什么不直接用Seata?
A:Seata AT模式有全局锁,性能开销大。最终一致性方案吞吐量高10倍以上,适合大多数互联网场景Q:本地消息表数据越来越大怎么办?
A:1)已发送的消息定期归档 2)分表存储 3)使用RocketMQ事务消息替代本地消息表
题目七:如何设计一个限流方案?
我的回答
// 四种限流算法,各有适用场景// 1. 令牌桶(最常用)—— Guava RateLimiterprivatefinalRateLimiterlimiter=RateLimiter.create(100);// 100 QPSpublicResponsehandle(Requestrequest){if(!limiter.tryAcquire(1,500,TimeUnit.MILLISECONDS)){returnResponse.tooManyRequests("系统繁忙,请稍后重试");}returndoHandle(request);}// 2. 滑动窗口 —— Redis + Lua(分布式限流)publicbooleanisAllowed(Stringkey,intmaxRequests,intwindowSeconds){StringluaScript=""" local current = redis.call('INCR', KEYS[1]) if current == 1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end if tonumber(current) > tonumber(ARGV[1]) then return 0 end return 1 """;Longresult=redisTemplate.execute(newDefaultRedisScript<>(luaScript,Long.class),Collections.singletonList("rate:"+key),String.valueOf(maxRequests),String.valueOf(windowSeconds));returnresult!=null&&result==1;}// 3. Sentinel(阿里开源)—— 支持多种限流策略@SentinelResource(value="queryOrder",blockHandler="queryOrderBlocked")publicOrderqueryOrder(LongorderId){returnorderService.findById(orderId);}publicOrderqueryOrderBlocked(LongorderId,BlockExceptionex){returnOrder.degraded();// 降级返回}题目八:线上接口突然变慢,怎么排查?
我的回答(排查SOP)
1分钟内:看监控 → CPU/内存/GC是否异常? → 是单个接口慢还是所有接口慢? → DB慢查询日志有没有新增? 5分钟内:定位瓶颈 → 链路追踪(SkyWalking/Zipkin)看耗时分布 → 是DB慢?Redis慢?外部API慢?GC停顿? 15分钟内:紧急处理 → 单接口慢:限流降级 → DB慢:查执行计划,是否索引失效 → GC频繁:dump内存分析,是否有内存泄漏 事后复盘: → 为什么没有提前发现? → 监控告警是否覆盖了这个场景? → 如何避免再次发生?// 常见性能问题代码示例// 问题1:N+1查询// ❌List<Order>orders=orderDao.findAll();for(Orderorder:orders){Useruser=userDao.findById(order.getUserId());// N+1次查询!}// ✅List<Order>orders=orderDao.findAll();Set<Long>userIds=orders.stream().map(Order::getUserId).collect(toSet());Map<Long,User>userMap=userDao.findByIdIn(userIds).stream().collect(toMap(User::getId,Function.identity()));// 问题2:大对象序列化// ❌ 返回全部字段returnResponseEntity.ok(order);// ✅ 只返回需要的字段returnResponseEntity.ok(OrderVO.from(order));// 问题3:锁粒度过大// ❌publicsynchronizedvoidupdateUser(Useruser){// 整个方法加锁}// ✅publicvoidupdateUser(Useruser){synchronized(("user:"+user.getId()).intern()){// 只锁当前用户}}面试心得
- 架构题没有标准答案,但有回答框架:先说方案 → 再说取舍 → 最后说追问
- 用真实项目经历背书:每个方案都结合自己的项目说"我们在xxx场景下是这样做的"
- 主动说出方案的缺点:面试官最怕听到"完美方案",说出trade-off反而加分
- 准备3个深度案例:一个高并发、一个分布式、一个性能优化,覆盖80%的架构题
写在最后
架构面试考的不是背八股文,而是解决实际问题的思维能力。9年开发经验告诉我,真正好的架构不是设计出来的,是在解决实际问题中演进出来的。
准备面试的过程,也是梳理自己知识体系的过程。与其焦虑,不如把每个问题想透。
📌我是卷毛,9年Java开发,专注技术成长和面试经验分享。
这8道题如果对你有帮助,收藏+关注,面试前翻出来复习。
后续会持续分享架构设计实战和大厂面试系列。
《卷毛的技术笔记》—— 一起卷出技术力。🔥
你面试遇到过什么架构题?评论区交流 👇