蔚来 AI应用开发 一面(暑期)
1. 自我介绍,结合一个复杂项目讲清楚你负责的核心链路
2. SSE 是怎么实现的,服务端和客户端分别要做什么
SSE 本质上是服务端基于 HTTP 长连接向客户端持续推送文本事件。客户端通过 EventSource 发起请求,服务端返回 Content-Type: text/event-stream,然后按照 SSE 规定的格式不断写入数据。每条消息通常由 event、id、data 组成,消息之间用空行分隔。
SSE 适合 AI 流式输出、任务进度推送、实时日志、诊断过程展示这类服务端单向推送场景。它比 WebSocket 简单,浏览器原生支持自动重连,但不适合强双向通信。如果后端经过 Nginx,还要关闭代理缓冲,否则前端可能收不到实时片段。
@GetMapping(value = "/diagnosis/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamDiagnosis(@RequestParam String taskId) {
SseEmitter emitter = new SseEmitter(60_000L);
executor.execute(() -> {
try {
emitter.send(SseEmitter.event().name("message").data("开始读取车辆故障码"));
emitter.send(SseEmitter.event().name("message").data("正在匹配故障知识库"));
emitter.send(SseEmitter.event().name("message").data("正在生成维修建议"));
emitter.send(SseEmitter.event().name("done").data("finish"));
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
3. SSE 连接很多时,后端为什么容易出问题,怎么做连接治理
SSE 是长连接,连接持续期间会占用文件描述符、连接对象、内存缓冲、线程或协程资源。如果服务端用一个连接绑定一个阻塞线程的模型,连接数一上来线程池就会被耗尽。另一个常见问题是慢客户端导致写缓冲积压,服务端一直发不出去,内存会被拖垮。
治理上要做连接数限制、心跳、超时、断开感知、消息队列缓冲上限和任务取消。服务端要监听客户端断开,一旦连接断了,后台诊断流如果不需要继续,就要及时取消。生产里还要设置网关超时、关闭代理缓冲、按用户或租户限流,避免一个用户开大量 SSE 连接。
public class StreamSession {
private final String taskId;
private final SseEmitter emitter;
private final AtomicBoolean closed = new AtomicBoolean(false);
public void close() {
if (closed.compareAndSet(false, true)) {
emitter.complete();
}
}
public boolean isClosed() {
return closed.get();
}
}
4. RabbitMQ 怎么保证消息的时序性,单队列 FIFO 为什么还不够
RabbitMQ 单个队列内部基本是 FIFO 的,但业务时序性不能只靠“队列先进先出”。如果多个消费者同时消费同一个队列,消息会被并发分发,处理完成顺序就不一定和投递顺序一致。即使只有一个消费者,如果消费失败重回队列、死信转发、重试延迟,也可能改变业务上的观察顺序。
要保证同一业务对象的顺序,通常要按业务 key 路由到同一个队列,并且这个队列对该 key 使用单消费者或串行处理。比如同一辆车的故障状态事件必须按上报顺序处理,可以把 vehicleId 作为路由 key。全局有序代价非常高,工程里一般只保证同一业务键局部有序。
// 根据 vehicleId 做一致性路由,保证同一车辆进入同一条处理队列
public String routeQueue(String vehicleId, int queueCount) {
int index = Math.abs(vehicleId.hashCode()) % queueCount;
return "vehicle.event.queue." + index;
}
5. RabbitMQ 的消息确认、持久化和幂等分别解决什么问题
消息确认解决的是“消费者处理完没有”。手动 ack 只有在业务处理成功后才确认,失败时可以 nack 或 reject。消息持久化解决的是 Broker 异常重启时消息不丢,需要队列持久化、消息持久化以及合理的刷盘策略。幂等解决的是“消息可能重复投递”,因为 RabbitMQ 只能尽量保证投递,不会替业务保证只处理一次。
这三件事经常被混在一起,但边界完全不同。ack 不代表业务不会重复,持久化不代表消费一定成功,幂等也不代表消息不会丢。生产系统一般按“至少一次投递”设计,用业务唯一键或消费日志做去重。
CREATE TABLE mq_consume_record (
msg_id VARCHAR(64) PRIMARY KEY,
biz_key VARCHAR(128) NOT NULL,
status TINYINT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
public void consume(Message msg) {
String msgId = msg.getMessageProperties().getMessageId();
if (consumeRecord.exists(msgId)) {
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
return;
}
try {
handleBusiness(msg);
consumeRecord.insertSuccess(msgId);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
}
}
6. IoC 和 AOP 分别解决什么问题,它们在 Spring 里是怎么落地的
IoC 解决对象创建和依赖管理的问题。原来对象由业务代码自己 new,依赖关系也写死在代码里,IoC 把对象创建、依赖注入和生命周期管理交给容器,业务代码只关心使用。Spring 里 BeanDefinition 描述对象,BeanFactory 负责创建 Bean,依赖注入把 Bean 之间的关系装配起来。
AOP 解决横切逻辑复用的问题。日志、鉴权、事务、限流、监控这类逻辑如果散落在业务方法里,会导致大量重复代码和强耦合。AOP 通过代理把横切逻辑织入目标方法调用前后,Spring 里常见的是 JDK 动态代理和 CGLIB 代理。事务注解本质上就是典型 AOP 场景。
@Aspect
@Component
public class TraceAspect {
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.
查看13道真题和解析