后端技术高频面试题
这篇更偏后端工程能力,不只讲接口怎么设计,还覆盖运行时、框架、中间件、并发、消息队列、锁、稳定性和排障。
后端技术面试能力链路
回答后端题时,最好能从“请求进来”一路讲到“数据正确、服务稳定、问题可排查”。
Node.js 事件循环是什么?
Node.js 事件循环是它处理异步任务的核心机制。主线程执行同步代码,遇到文件 I/O、网络请求、定时器等异步任务时交给底层系统处理,完成后把回调放回事件循环等待执行。
面试时可以这样说:Node.js 适合 I/O 密集型服务,因为它不用为每个请求创建一个线程,而是通过事件循环和非阻塞 I/O 处理大量并发连接。
需要注意:事件循环不代表没有阻塞。如果在主线程里做大量 CPU 计算,比如大 JSON 解析、图片处理、加密循环,也会阻塞所有请求。
Node.js 适合做 CPU 密集型任务吗?
不太适合直接在主线程做 CPU 密集任务。Node.js 的优势是 I/O 并发,如果主线程被大计算占满,事件循环无法及时处理新请求,接口就会变慢。
常见处理方式:
- 把 CPU 密集任务放到 Worker Threads。
- 交给独立任务服务处理。
- 使用消息队列异步处理。
- 对图片、视频、报表等任务做离线生成。
面试回答可以补一句:Node.js 可以做后端服务,但要避免把所有重计算都放在请求线程里。
Express、Koa、NestJS 有什么区别?
Express 是经典 Web 框架,生态成熟,上手快,适合小到中型服务。Koa 更轻量,基于 async/await,中间件模型更现代,但需要自己组合更多能力。
NestJS 更偏企业级架构,内置模块、依赖注入、装饰器、管道、守卫、拦截器等概念,适合大型项目和团队协作。
选择时可以这样说:
- 小服务或快速原型:Express / Koa。
- 团队项目、模块多、需要规范:NestJS。
- 已有历史项目:优先遵循现有技术栈,不轻易重写。
中间件机制是什么?
中间件是在请求到达业务处理函数前后执行的一段逻辑。它可以处理日志、鉴权、参数解析、跨域、限流、错误捕获等通用能力。
以洋葱模型为例,请求进入时按顺序经过多个中间件,响应返回时再反向经过中间件。
request -> logger -> auth -> validator -> controller -> response好的中间件应该职责单一,不要把具体业务写进通用中间件,否则会越来越难维护。
Controller、Service、Repository 怎么分层?
常见分层是 Controller 负责接收请求和返回响应,Service 负责业务逻辑,Repository 或 DAO 负责数据访问。
这样做的好处是职责清晰:
- Controller 不写复杂业务。
- Service 不关心 HTTP 细节。
- Repository 不关心业务流程。
例如创建订单时,Controller 校验请求格式,Service 处理库存、金额、状态机,Repository 负责读写订单表和库存表。
参数校验应该放在哪里?
参数校验应该分层处理。前端做体验校验,后端做可信校验。后端又可以分为请求参数校验和业务规则校验。
请求参数校验包括类型、必填、长度、格式,例如手机号、邮箱、分页参数。业务规则校验包括库存是否足够、用户是否有权限、订单状态是否允许修改。
面试时要强调:所有来自客户端的输入都不可信,后端必须校验。
ORM 有什么优缺点?
ORM 可以把数据库表映射成对象,减少手写 SQL,提高开发效率,也更容易做类型提示和迁移管理。
缺点是复杂查询可能不直观,性能问题容易被隐藏,生成的 SQL 不一定最优。遇到慢查询时,仍然要能看懂 SQL 和执行计划。
成熟回答是:普通 CRUD 可以用 ORM,复杂统计、性能敏感查询可以写原生 SQL,并保留慢查询监控。
数据库连接池是什么?
连接池是提前维护一组数据库连接,请求来了直接复用连接,避免频繁创建和销毁连接。
关键参数包括最大连接数、最小空闲连接、连接超时、空闲回收和健康检查。连接池太小会导致请求排队,太大会压垮数据库。
排查连接池问题时,可以看活跃连接数、等待队列、慢 SQL、事务是否长时间不提交、连接是否没有释放。
乐观锁和悲观锁有什么区别?
悲观锁认为冲突经常发生,所以操作前先加锁,例如数据库 select for update。它一致性强,但并发性能可能下降。
乐观锁认为冲突不常发生,更新时检查版本号或更新时间。例如 where id = ? and version = ?,更新成功后版本号加一。
常见选择:
- 库存扣减、余额更新:可以用事务和锁严格控制。
- 普通编辑表单:可以用乐观锁提示“数据已被他人修改”。
分布式锁怎么实现?
分布式锁用于多实例服务之间协调同一资源,例如防止多个节点同时处理同一个任务。常见实现是 Redis 锁、数据库锁或 ZooKeeper 锁。
Redis 锁要注意:
- 使用原子命令加锁。
- 设置过期时间,避免死锁。
- 释放锁时校验 value,避免释放别人的锁。
- 锁超时要和业务执行时间匹配。
面试时可以补充:分布式锁不是万能方案,能用唯一约束、状态机、幂等设计解决的,优先用更简单可靠的方式。
消息队列解决什么问题?
消息队列主要解决异步解耦、削峰填谷和失败重试。比如用户下单后,发送短信、发邮件、更新积分、生成报表都可以异步处理。
它的价值是让主流程更快、更稳,但也会引入最终一致性、消息重复、消息丢失和顺序问题。
回答时可以举例:下单接口只完成订单创建和核心事务,后续通知通过队列异步发送,失败后重试,不影响用户下单成功。
如何保证消息不丢失?
要从生产者、队列和消费者三段保证。生产者发送后要确认成功;队列要持久化消息;消费者处理成功后再 ack。
常见手段:
- 生产者 confirm。
- 消息持久化。
- 消费者手动 ack。
- 失败重试和死信队列。
- 业务幂等,防止重复消费。
面试时要说明:完全不丢和绝对不重复成本很高,业务上通常追求“至少一次投递 + 消费端幂等”。
如何处理消息重复消费?
消息重复消费很常见,所以消费者必须幂等。可以使用业务唯一键、消息 ID、订单状态机、数据库唯一索引来避免重复处理。
例如支付成功消息重复到达时,先检查订单是否已支付。如果已经支付,就直接返回成功,不再重复扣库存或发权益。
关键原则是:不要假设消息只会来一次,消费者要能安全处理多次。
死信队列是什么?
死信队列用于存放多次消费失败、过期或被拒绝的消息。它不是失败终点,而是排查和补偿入口。
例如发送短信失败重试 3 次仍失败,就进入死信队列。运维或后台任务可以查看失败原因,修复后重新投递。
面试时可以说:死信队列要配合告警和人工处理流程,否则只是把问题藏到另一个队列里。
什么是熔断、降级和限流?
限流是限制请求量,防止服务被打爆。熔断是在下游服务持续失败时,暂时停止调用它,避免故障扩散。降级是在核心功能不可用时,用简化方案保证主要流程可用。
例如推荐服务挂了,商品详情页可以不展示推荐商品,这就是降级。支付服务异常时则不能简单降级,需要阻断交易并提示用户稍后再试。
面试回答要结合业务重要性:不是所有功能都能降级,核心数据正确性优先。
后端如何做限流?
限流可以按 IP、用户、租户、接口、API key 等维度做。常见算法有固定窗口、滑动窗口、令牌桶和漏桶。
工程上常放在网关层、服务层或 Redis 中。登录、短信、搜索、上传、AI 调用、导出等接口都适合限流。
好的限流设计要给出明确错误码和重试提示,不能只是让请求随机失败。
如何设计后台任务系统?
后台任务系统通常包括任务表、任务状态、执行器、重试机制、超时控制、日志和告警。
任务状态可以设计为:
pending -> running -> success
-> failed -> retrying -> dead关键点是任务幂等、失败重试、并发控制和可观测性。报表导出、批量导入、图片处理、AI 文档解析都适合放到后台任务。
如何排查接口偶发 500?
先确认影响范围和发生时间,再通过 requestId 查日志。重点看错误栈、入参、用户、版本、数据库、缓存、第三方服务和最近发布。
如果是偶发问题,要特别关注并发、空值、超时、连接池耗尽、缓存击穿、第三方抖动。先止血,再定位根因。
面试时可以说:线上排障不要只看代码,要把日志、监控、链路追踪和发布记录串起来。
如何排查服务内存泄漏?
先看内存是否持续上涨且不会回落,再结合堆快照、监控和代码变更排查。Node.js 中常见原因包括全局缓存不清理、事件监听未移除、定时器未清理、大对象被闭包引用。
处理方式:
- 观察 RSS 和 heap 使用情况。
- 生成 heap snapshot。
- 对比对象增长来源。
- 检查缓存、队列、Map、事件监听和定时任务。
临时止血可以重启或扩容,但最终要找到泄漏对象。
什么是优雅关闭?
优雅关闭是服务收到退出信号后,不立刻中断,而是停止接收新请求,等待正在处理的请求完成,再关闭数据库、缓存、队列连接。
常见流程:
- 接收
SIGTERM。 - 停止接收新流量。
- 等待存量请求完成。
- 关闭连接和后台任务。
- 超时后强制退出。
容器化部署、滚动发布和自动扩缩容都需要优雅关闭,否则用户请求可能在发布时被中断。
日志、指标、链路追踪有什么区别?
日志用于记录具体事件,例如某个请求失败的错误栈。指标用于观察趋势,例如 QPS、错误率、P95 延迟、CPU、内存。链路追踪用于还原一次请求跨多个服务的调用路径。
三者配合使用:
- 指标发现问题。
- 链路追踪定位慢在哪一段。
- 日志查看具体错误原因。
面试时可以强调:没有可观测性,后端服务上线后就像黑盒,出了问题只能猜。
单体应用和微服务怎么选?
单体应用部署简单、开发成本低、事务边界清晰,适合早期项目和团队规模较小的业务。微服务适合业务边界清晰、团队较多、需要独立扩展和独立发布的场景。
微服务会带来服务治理、分布式事务、链路追踪、配置管理、网络失败等复杂度。
成熟回答是:先把单体内部模块边界做好,等业务规模和团队协作真的需要时,再按领域逐步拆服务。
gRPC 和 REST 怎么选?
REST 基于 HTTP 和 JSON,通用性强,调试方便,适合对外 API、前后端接口和开放平台。
gRPC 基于 HTTP/2 和 Protobuf,性能更好,接口契约更强,适合服务间调用和内部高性能通信。
选择时可以说:外部接口优先 REST,内部服务间高频调用可以考虑 gRPC,但要评估团队工具链和调试成本。
WebSocket、SSE 和轮询怎么选?
轮询实现简单,但实时性和性能一般。SSE 是服务端单向推送到浏览器,适合通知、日志流、AI 流式输出。WebSocket 是双向通信,适合聊天、协作、实时游戏、实时看板。
如果只是低频状态更新,轮询足够。如果需要服务端持续推送,SSE 更简单。如果客户端和服务端都要高频互发,选 WebSocket。
面试时要补充:实时通信也要考虑鉴权、断线重连、心跳、消息顺序和限流。