不借助框架魔法,后端是如何工作的
最近一次面试中,我问一位候选人:当 Node.js 应用容器在 Kubernetes 中重启时,活动中的 HTTP 请求会怎样处理。这位候选人在 NestJS 中写了三年代码,构建过微服务,却对这个问题一头雾水。他们从未考虑过操作系统信号、数据库超时和服务器关闭等问题。框架将这些细节隐藏在幕后,直到每次部署时生产环境开始出现 500 错误。
我们习惯了用现成的构建块来搭建后端。添加几个装饰器,连接一个 ORM,按下按钮,API 就能响应客户端。当现成的抽象失效或服务触及内存和 CPU 限制时,困难就开始了。
这个问题在仓库 Backend from First Principles 中有所涉及,由开发者 DsThukurRawat 创建。作者编写了一份工程参考指南,将服务器开发拆解为基本原理。
仓库里有什么
该项目分为 24 个主题,包含笔记、架构图和 JavaScript 代码示例。该仓库在 GitHub 上有约三百颗星,还有一个独立的文档站点。这不是一门臃肿的商业课程,而是一位工程师整理自身知识的开放笔记。
材料按顺序组织。先讲协议、序列化和路由,然后是架构分层,接近末尾部分作者转向分布式系统、消息代理和扩展。
该项目的主旨很简单:当你理解了过程的底层原理,任何新框架都能在几个晚上内掌握。
参考指南中的四个主题
与其枯燥地总结目录,不如看看作者涉及紧迫工程问题的四个主题。
优雅关闭应用
在典型的入门教程中,服务器用 app.listen(3000) 启动,代码到此为止。在生产环境中,服务器在部署期间、内存不足时或计划节点维护期间会不断重启。
该仓库详细展示了如何组织优雅关闭。当进程收到来自操作系统的 SIGTERM 信号时,应用执行一系列步骤:
- 停止接受新的传入 HTTP 连接;
- 给已在处理中的请求留出完成时间;
- 关闭数据库和缓存的连接池;
- 退出前将日志缓冲区刷新到磁盘。
作者提供了一个在 Node.js 中拦截系统信号的清晰示例:
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
function shutdown(signal) {
console.log(`Received ${signal}. Shutting down gracefully...`);
server.close(() => {
console.log('HTTP server closed');
// Закрываем подключения к базе данных
db.pool.end(() => {
console.log('Database connections closed');
process.exit(0);
});
});
// Принудительное завершение, если запросы зависли
setTimeout(() => {
console.error('Forced shutdown due to timeout');
process.exit(1);
}, 10000);
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
这种方法可防止服务更新期间出现事务中断和用户数据损坏。
序列化和 CPU 负载
许多人认为 JSON.stringify() 和 JSON.parse() 几乎是免费实现的。在每秒十个请求时,差异确实不明显。当数千个请求通过服务时,文本解析开始消耗可观的 CPU 时间。
在序列化部分,作者详细分析了不同格式的区别:
- JSON 等文本结构;
- Protocol Buffers 等二进制协议;
- 验证和转换传入模式的开销;
- 数据量对网络吞吐量的影响。
它清楚地展示了为什么工程师在内部微服务通信中选择 gRPC 和二进制格式,而不是基于 JSON 的纯 REST。
架构分层,不添不必要的复杂性
开发者经常混写逻辑:直接在控制器中写 SQL 查询,或把参数验证放在服务层。
该仓库涵盖了经典的分层方法:
- 控制器接收传入的 HTTP 请求并形成响应;
- 服务实现业务规则和应用逻辑;
- 仓储层负责数据库交互;
- 中间件检查授权、记录指标并丰富请求上下文。
清晰的层分离有助于隔离测试业务逻辑,无需启动数据库和发起真实网络调用。
异步任务和队列
如果用户需要生成复杂的 PDF 报告或处理视频,在 HTTP 请求内完成是不可行的。客户端会收到超时错误,服务工作线程会被阻塞。
该指南涵盖了后台任务工作流模式:
- 将作业保存到消息代理队列;
- 向客户端返回带有任务标识符的 202 Accepted 响应;
- 由单独池中的工作线程异步执行;
- 失败重试策略以及将问题消息发送到死信队列。
缺点和粗糙之处
该项目相对较新,因此存在局限性。
几乎所有代码都是用 JavaScript 编写的。如果你的主要技术栈是 Go、Java、Rust 或 C#,你需要为类型和平台特性进行心智适配。作者在 README 中明确表示欢迎社区帮助提供其他语言的示例。
一些主题目前读起来更像是简短的论点而非全面的文章。如果能看到更多真实的事故分析和带图表的性能基准测试就好了。
谁会从这个仓库受益
该项目可在四种实际场景中使用:
- 想要理解网络协议、操作系统信号和数据库结构的初级开发者。
- 准备系统设计和技术面试的工程师。
- 从单体架构转向分布式服务的开发者。
- 技术负责人用于制定开发计划或帮助新团队成员入职。
最终思考
DsThakurRawat 的参考指南帮助你将后端视为一组可理解的工程原则,而非库魔法。你不必从头到尾研读它。
收藏该仓库,在设计架构、在 Redis 中配置缓存或准备将微服务部署到 Kubernetes 时,打开相关章节。
相关项目