Skip to content

后端技术高频面试题

这篇更偏后端工程能力,不只讲接口怎么设计,还覆盖运行时、框架、中间件、并发、消息队列、锁、稳定性和排障。

后端技术面试能力链路

请求入口路由、中间件、鉴权、限流
->
业务处理参数校验、事务、锁、幂等
->
异步解耦消息队列、重试、死信、补偿
->
稳定运行日志、监控、熔断、优雅关闭

回答后端题时,最好能从“请求进来”一路讲到“数据正确、服务稳定、问题可排查”。

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。
  • 已有历史项目:优先遵循现有技术栈,不轻易重写。

中间件机制是什么?

中间件是在请求到达业务处理函数前后执行的一段逻辑。它可以处理日志、鉴权、参数解析、跨域、限流、错误捕获等通用能力。

以洋葱模型为例,请求进入时按顺序经过多个中间件,响应返回时再反向经过中间件。

text
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 调用、导出等接口都适合限流。

好的限流设计要给出明确错误码和重试提示,不能只是让请求随机失败。

如何设计后台任务系统?

后台任务系统通常包括任务表、任务状态、执行器、重试机制、超时控制、日志和告警。

任务状态可以设计为:

text
pending -> running -> success
                 -> failed -> retrying -> dead

关键点是任务幂等、失败重试、并发控制和可观测性。报表导出、批量导入、图片处理、AI 文档解析都适合放到后台任务。

如何排查接口偶发 500?

先确认影响范围和发生时间,再通过 requestId 查日志。重点看错误栈、入参、用户、版本、数据库、缓存、第三方服务和最近发布。

如果是偶发问题,要特别关注并发、空值、超时、连接池耗尽、缓存击穿、第三方抖动。先止血,再定位根因。

面试时可以说:线上排障不要只看代码,要把日志、监控、链路追踪和发布记录串起来。

如何排查服务内存泄漏?

先看内存是否持续上涨且不会回落,再结合堆快照、监控和代码变更排查。Node.js 中常见原因包括全局缓存不清理、事件监听未移除、定时器未清理、大对象被闭包引用。

处理方式:

  • 观察 RSS 和 heap 使用情况。
  • 生成 heap snapshot。
  • 对比对象增长来源。
  • 检查缓存、队列、Map、事件监听和定时任务。

临时止血可以重启或扩容,但最终要找到泄漏对象。

什么是优雅关闭?

优雅关闭是服务收到退出信号后,不立刻中断,而是停止接收新请求,等待正在处理的请求完成,再关闭数据库、缓存、队列连接。

常见流程:

  1. 接收 SIGTERM
  2. 停止接收新流量。
  3. 等待存量请求完成。
  4. 关闭连接和后台任务。
  5. 超时后强制退出。

容器化部署、滚动发布和自动扩缩容都需要优雅关闭,否则用户请求可能在发布时被中断。

日志、指标、链路追踪有什么区别?

日志用于记录具体事件,例如某个请求失败的错误栈。指标用于观察趋势,例如 QPS、错误率、P95 延迟、CPU、内存。链路追踪用于还原一次请求跨多个服务的调用路径。

三者配合使用:

  • 指标发现问题。
  • 链路追踪定位慢在哪一段。
  • 日志查看具体错误原因。

面试时可以强调:没有可观测性,后端服务上线后就像黑盒,出了问题只能猜。

单体应用和微服务怎么选?

单体应用部署简单、开发成本低、事务边界清晰,适合早期项目和团队规模较小的业务。微服务适合业务边界清晰、团队较多、需要独立扩展和独立发布的场景。

微服务会带来服务治理、分布式事务、链路追踪、配置管理、网络失败等复杂度。

成熟回答是:先把单体内部模块边界做好,等业务规模和团队协作真的需要时,再按领域逐步拆服务。

gRPC 和 REST 怎么选?

REST 基于 HTTP 和 JSON,通用性强,调试方便,适合对外 API、前后端接口和开放平台。

gRPC 基于 HTTP/2 和 Protobuf,性能更好,接口契约更强,适合服务间调用和内部高性能通信。

选择时可以说:外部接口优先 REST,内部服务间高频调用可以考虑 gRPC,但要评估团队工具链和调试成本。

WebSocket、SSE 和轮询怎么选?

轮询实现简单,但实时性和性能一般。SSE 是服务端单向推送到浏览器,适合通知、日志流、AI 流式输出。WebSocket 是双向通信,适合聊天、协作、实时游戏、实时看板。

如果只是低频状态更新,轮询足够。如果需要服务端持续推送,SSE 更简单。如果客户端和服务端都要高频互发,选 WebSocket。

面试时要补充:实时通信也要考虑鉴权、断线重连、心跳、消息顺序和限流。

Built with VitePress and GitHub Pages.