蔚来 AI应用开发 一面(暑期)

1. 自我介绍,结合一个复杂项目讲清楚你负责的核心链路

2. SSE 是怎么实现的,服务端和客户端分别要做什么

SSE 本质上是服务端基于 HTTP 长连接向客户端持续推送文本事件。客户端通过 EventSource 发起请求,服务端返回 Content-Type: text/event-stream,然后按照 SSE 规定的格式不断写入数据。每条消息通常由 eventiddata 组成,消息之间用空行分隔。

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面试实战专栏 文章被收录于专栏

本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.

全部评论

相关推荐

不愿透露姓名的神秘牛友
今天 00:23
長谷川育美offic...:一般没有10w我是要紫砂的
点赞 评论 收藏
分享
今天 00:20
长沙学院 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务