美团 AI应用开发 二面
1. 你现在做的系统如果让我一句话理解,它解决的核心问题是什么
2. 如果让你重新设计这套系统,你会先拆哪几个边界,而不是先画微服务图
先拆交易主链路和旁路能力。主链路只保留强约束动作,比如下单、库存冻结、支付确认、履约状态推进;旁路能力像风控、推荐、画像、报表、运营分析都要尽量异步化。再往下拆数据边界,订单事实、库存事实、支付事实不能混成一张万能表。最后才是服务边界,因为很多系统不是服务没拆好,而是业务一致性边界一开始就画错了。
3. 一套交易系统里,什么地方最容易被误判成“性能问题”,其实是模型设计问题
最典型的是状态字段过载。很多系统把几十种业务状态塞进一个 status,然后所有查询、补偿、回查、对账都围着它转,最后看起来是 SQL 慢、索引难建、缓存难做,本质上是状态模型坏了。还有库存口径混乱、订单快照和实时事实不分、幂等记录和业务记录耦合,这些都会把业务复杂度伪装成性能问题。
4. 你怎么设计订单状态机,才能避免补偿逻辑把系统越修越乱
状态机必须先定义合法迁移,而不是先写补偿任务。真正稳定的设计是每个状态都只接受有限前驱,非法迁移直接拒绝或者忽略,幂等更新必须带版本或条件。补偿只是为了让系统收敛,不应该允许任意回退和覆盖,否则越补越乱。很多线上脏数据,本质上就是状态机没有“禁止迁移”的能力。
public boolean canTransit(OrderStatus from, OrderStatus to) {
return switch (from) {
case INIT -> to == OrderStatus.PAID || to == OrderStatus.CANCELLED;
case PAID -> to == OrderStatus.ALLOCATED || to == OrderStatus.REFUNDING;
case ALLOCATED -> to == OrderStatus.DELIVERING || to == OrderStatus.REFUNDING;
case DELIVERING -> to == OrderStatus.FINISHED || to == OrderStatus.REFUNDING;
default -> false;
};
}
5. 为什么很多高并发系统最后会死在“局部强一致执念”上
因为不是所有链路都值得同步阻塞。很多人希望下单、库存、支付、优惠、积分、营销、通知一次请求里全做完,这样表面上看结果最完整,实际上把系统弹性完全打没了。交易系统真正重要的是核心事实先落稳,其他副作用异步扩散,并且允许最终一致。局部强一致可以有,但必须非常克制,只放在真正承担资金、库存、资格这类关键约束的位置。
6. 如果消息队列突然从“稳定削峰”变成“放大故障”,你会优先怀疑什么
优先怀疑消费逻辑的副作用扩散,而不是 broker 本身。比如消费端依赖某个下游接口变慢、失败重试没有退避、批量消费里混入慢消息、分区热点导致局部积压、或者消息模型里没有隔离高价值与低价值事件。MQ 很少凭空把系统打挂,通常是它把后端的不稳定放大了。
7. 你怎么保证消息消费幂等,不要只说“加唯一索引”
唯一索引只是落库层幂等的一种兜底,远远不够。真正完整的幂等要定义消息唯一键、消费语义、重试边界和副作用去重方式。比如更新类消息通常要校验版本,通知类消息要记录投递痕迹,转账类消息要绑定业务单号。幂等不是为了防止重复消息,而是为了让系统面对重复、乱序、重放时仍然能收敛到同一个结果。
create table biz_consume_log (
msg_key varchar(64) primary key,
biz_type varchar(32) not null,
consume_time datetime not null
);
8. 一个链路要求“同订单严格有序、不同订单尽量并行”,你会怎么做
这类场景通常按订单号或聚合根 ID 做分区,让同一 key 落到同一有序队列里,不同 key 之间并行消费。问题不在“怎么 hash”,而在热点 key 处理和失败阻塞。如果某个大订单产生大量事件,同分区会被拖慢,所以业务上要把真正需要严格顺序的步骤收窄,别把所有边缘事件都强行放进同一顺序流里。
9. 你怎么看缓存一致性,不接受“先删缓存再写数据库”这种背诵式回答
缓存一致性没有银弹,只有按场景选代价。读多写少时可以接受短暂不一致,优先做延迟双删或异步失效;写强约束场景更适合缓存只做派生视图,不做事实来源;热点场景要防击穿、防回源并发、还要考虑多级缓存观察到的版本差异。真正成熟的回答不是背模式,而是先明确谁才是权威数据源,以及业务能接受多长时间的不一致。
10. Redis 热点 key 把单分片打满了,你怎么止血
先识别热点是不是业务必然热点。如果是少数爆款商品、活动资格、库存 key,不能只靠扩容 Redis,要先做热点隔离、本地缓存短时兜底、请求合并和令牌桶削峰。再往后才是 key 打散、逻辑分桶和读写分离。因为单个热点如果不在业务入口处限流,底层分片再怎么扩都容易被同一波流量打穿。
public String queryHotKey(String key) {
String local = localCache.get(key);
if (local != null) return local;
synchronized (("LOCK_" + key).intern()) {
String again = localCache.get(key);
if (again != null) return again;
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value, 500);
}
return value;
}
}
11. MySQL 在订单场景里最常见的慢,不是 CPU 打满,而是什么
更常见的是错误访问模式。比如深分页、回表过多、范围扫描和排序条件不匹配、冷热数据混表、宽表把索引拖大、还有事务持锁时间过长导致隐性阻塞。数据库“慢”很多时候不是某条 SQL 突然变差,而是业务流量、表膨胀和访问习惯逐渐把它推到临界点。
12. 如果订单表已经几亿数据了,但又不能立刻分库分表,你先怎么救
先做访问分层,而不是一上来拆库。把在线链路、回查链路、运营查询、对账查询拆开;历史数据归档,主表只留高频访问范围;深分页改成游标式翻页;高频查询做覆盖索引或快照表;必要时把复杂聚合搬到异步离线视图。很多系统在真正分库前,还有大量低成本但高收益的治理空间。
13. 页面打开很慢,前端说后端慢,后端说数据库慢,这种互相甩锅你怎么切
最有效的是拿一条完整 trace 把时间拆开。先看 DNS、TLS、网关、鉴权、聚合接口、缓存命中、RPC、数据库和前端渲染各占多少,再看是否存在串行调用和某个组件尾延迟异常。线上排查不能只盯平均值,P99 和长尾请求更关键。很多“后端慢”其实是前端串行请求造成的,也有很多“数据库慢”其实是上层重复回源。
14. 如果确认是网络抖动,不要只说丢包,你会展开到什么程度
我会继续拆成建连阶段、传输阶段和返回阶段。建连看 DNS、SYN 重传、TLS 握手;传输看 MTU、拥塞、队头阻塞和连接复用;返回看服务端 accept backlog、线程池堆积和连接池耗尽。很多网络问题表面像链路抖动,实质是服务端没来得及处理连接,或者上游使用短连接把下游打爆了。
15. 服务端线程池和连接池为什么经常比 CPU 更早出问题
因为它们是请求并发的硬闸门。CPU 还有调度和降速空间,但线程池排满后请求直接排队,连接池耗尽后调用直接卡死或超时,尾延
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.