当前位置: 首页 > news >正文

写了9年代码,我靠这8道架构题拿下了P7 offer

写了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()){// 只锁当前用户}}

面试心得

  1. 架构题没有标准答案,但有回答框架:先说方案 → 再说取舍 → 最后说追问
  2. 用真实项目经历背书:每个方案都结合自己的项目说"我们在xxx场景下是这样做的"
  3. 主动说出方案的缺点:面试官最怕听到"完美方案",说出trade-off反而加分
  4. 准备3个深度案例:一个高并发、一个分布式、一个性能优化,覆盖80%的架构题

写在最后

架构面试考的不是背八股文,而是解决实际问题的思维能力。9年开发经验告诉我,真正好的架构不是设计出来的,是在解决实际问题中演进出来的。

准备面试的过程,也是梳理自己知识体系的过程。与其焦虑,不如把每个问题想透。


📌我是卷毛,9年Java开发,专注技术成长和面试经验分享。

这8道题如果对你有帮助,收藏+关注,面试前翻出来复习。

后续会持续分享架构设计实战大厂面试系列

《卷毛的技术笔记》—— 一起卷出技术力。🔥

你面试遇到过什么架构题?评论区交流 👇

http://www.jsqmd.com/news/1116729/

相关文章:

  • PCF8591与PIC18F86J55的信号转换系统设计与实现
  • 终极指南:如何用PingFangSC字体包构建专业级中文Web排版系统
  • 2026年7月防火门厂家推荐攻略|防火门、工业提升门、堆积门、学校门、挡烟垂壁靠谱厂家甄选
  • sql语法- MyBatis 中 <association> 标签的作用 1对1的情况
  • HoRain云--C++命名空间:解决冲突的终极指南
  • 量子计算商业化进入关键阶段:2026年哪些行业已经率先实现应用落地?
  • 毫米波人体动作姿态分类数据集3057张12类别
  • AI教材写作新方案:利用AI工具,快速产出低查重、高质量的教材!
  • TB9051FTG与MKV42F64VLH16的直流电机静音驱动方案
  • 一张架构图看懂 CC Switch:AI Coding 工具链终于有了“控制中心”
  • 如何在Windows上轻松安装虚拟游戏控制器驱动:ViGEmBus完整指南
  • Linux系统安装与命令行入门实战指南
  • 生产环境监控方案,Prometheus 加 Grafana 可视化显卡状态
  • 从业复盘:展厅制作常见落地坑点与高效解决方案
  • AI 如何提升工程生产力:高管圆桌会议的关键洞察
  • GPT-5.5 上下文缓存怎么用?Token降本方案与代码实战指南
  • 基于STM32单片机土壤湿度计WIFI物联网云平台阿里云大棚智能浇花1(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • 高精度4-20mA电流环设计:基于DAC161S997与PIC18F86K90
  • U校园智能刷课工具终极指南:三步实现全自动答题
  • 用了三年CSS Grid,我今天才发现以前理解布局的方式全是错的
  • 工业级条形码扫描模块EM3080-W与STM32F745VG集成方案
  • wlanapi.dll 报错和无线网络有关吗?服务、驱动和系统文件一起查
  • YOLOv10模型改进-注意力机制-第43篇:YOLOv10改进策略【注意力机制】| SpatialTransformer注意力机制
  • EM3080-W条形码解码器与STM32嵌入式系统开发指南
  • 免费大模型镜像真相:成本、风险与可持续替代方案
  • 第 16 讲:Workflow:让复杂任务可控执行
  • Akagi麻将AI助手:实时分析引擎的架构解析与部署指南
  • 小白友好|OpenClaw 2.7.9 Windows 端自动化 AI 助手搭建步骤
  • WannaCry勒索病毒应急响应与数字取证实战指南
  • 别被“纯手工编程”情怀绑架!资深CTO坦白局:淘汰率最高的不是不会AI的人,而是不会评估AI输出的人