Skip to main content

Backend

📅 2026-04-03 ✏️ 2026-04-03 CS
No related notes

1 · Backend#

后端:一台监听 HTTP / WebSocket / gRPC 请求的服务器,负责提供静态内容、动态数据、业务逻辑和安全的数据访问。

浏览器为什么不能直接承担后端职责:原因包括浏览器沙箱限制、跨域限制、无法管理数据库连接池,以及客户端算力有限。

一个请求如何进入系统,如何通过认证和代码分层,如何读写数据、利用缓存、把慢任务异步化,最后形成一个可扩展的后端系统。

网络链路、HTTP 语义、认证授权、代码分层、数据库执行模型、缓存一致性、异步任务幂等

https://medium.com/@karthik.joshi103/backend-from-first-principles-036209a3049c https://medium.com/@karthik.joshi103/backend-from-first-principles-91eaf3720e38

1.1 · 总览:一个请求如何流经后端

一个典型请求大致会经过这条链路:

Client → DNS / Network → HTTP Router → Middleware → Controller → Service → Repository → DB / Cache / Queue

如果把后端拆开看,本质上是在解决 6 类问题:

  • 请求怎么进入系统
  • 请求怎么被安全地接受
  • 请求怎么落到代码结构里
  • 数据怎么存储和读取
  • 热点数据怎么加速
  • 慢任务怎么从主链路中剥离

1.2 · 1. 请求如何进入系统#

API 基础概念:

  • 静态路由 GET /users vs 动态路由 GET /users/:id
  • query 参数用于过滤/分页:GET /users?role=admin&page=2
  • 嵌套路由表达从属关系:GET /users/:id/orders
  • 版本化:/v1/users/v2/users
  • 序列化(对象→JSON)/ 反序列化(JSON→对象)

请求链路可以先粗略理解成:

  • 域名解析到 IP
  • 请求穿过网络到达服务器
  • Web server / gateway 接收连接
  • HTTP 路由层根据 method + path 找到对应处理逻辑
  • 进入业务代码,最后返回响应

CORS:浏览器对跨域请求的安全机制。简单请求(GET/POST + 简单头)直接发送并检查响应头;非简单请求先发 OPTIONS 预检请求,服务端返回 Access-Control-Allow-* 头后才发送真正请求。

1.2.1 · REST#

  • S: Web 服务需要一套通用的 API 风格
  • C: SOAP 重 XML、强契约、僵硬,开发和调试成本高
  • Q: 有没有更轻量、更符合 Web 语义的 API 风格?
  • A: REST 的六个约束
约束含义
客户端-服务器前后端职责分离
无状态每次请求携带完整信息,服务端不存会话
可缓存响应需标明是否可缓存
统一接口资源通过 URI 标识,用 HTTP 方法操作
分层系统客户端不需要知道是否经过代理/网关
按需代码可选,服务端可下发可执行代码

API 设计规范:资源名用复数、层级路径表达关系、单资源通过 ID 标识

GET    /users               # 列表
POST   /users               # 创建
GET    /users/:id           # 单个资源
PUT    /users/:id           # 全量更新
PATCH  /users/:id           # 部分更新
DELETE /users/:id           # 删除
GET    /users/:id/orders    # 从属资源
GET    /users?role=admin    # 过滤条件放 query 参数

1.3 · 2. 请求如何被系统安全地接受#

  • S: 系统需要知道”你是谁”(认证)和”你能做什么”(授权)
  • C: 从单体到分布式,从自有用户到第三方登录,需求不断演进
  • Q: 认证授权方案如何一步步演进,各自解决什么问题?
  • A: Session → JWT → OAuth 2.0 → OIDC,内部权限用 RBAC

Session(有状态会话)

  • 服务端存储会话数据,通过 Cookie 中的 Session ID 识别用户
  • 问题:横向扩展时需要 sticky session 或集中式 session 存储(如 Redis),增加架构复杂度

JWT(无状态认证)

  • 服务端签发 token,客户端每次请求携带,服务端只需验签不需存储
  • 坑:token 一旦签发无法主动撤销(除非维护黑名单,又回到有状态);需要设计 access token + refresh token 双 token 刷新机制;payload 不要放敏感信息(Base64 编码不是加密)

OAuth 2.0(委托授权)

  • 解决”授权别人代表用户访问资源”的问题
授权模式适用场景
Authorization CodeWeb 应用,最安全(配合 PKCE 也用于 SPA/移动端)
Client Credentials服务间调用,无用户参与

OpenID Connect(身份认证)

  • 在 OAuth 2.0 之上增加 ID Token,解决”用户是谁”的身份认证问题

RBAC(权限控制)

  • 业务系统内部的权限控制适合单独用 RBAC 管理(用户→角色→权限),不应完全耦合在登录体系里

可以把这一节记成两句话:

  • 认证回答”你是谁”
  • 授权回答”你能做什么”

1.4 · 3. 请求如何落到代码结构里#

  • S: 后端代码量增长后需要合理组织
  • C: 不分层会导致业务逻辑、数据访问、请求处理混在一起,难以测试和维护
  • Q: 如何划分职责边界?
  • A: 按 Controller → Service → Repository 分层,依赖方向单向向下
职责处理的数据类型
Middleware校验、认证、日志、数据转换原始请求/响应
Controller接收请求、调用 Service、返回响应DTO(Data Transfer Object)
Service业务逻辑、事务编排Domain Model
Repository数据访问、SQL/ORM 操作Entity / DB Record

关键原则:

  • 依赖方向:Controller → Service → Repository,上层依赖下层,下层不感知上层
  • DTO vs Domain Model:DTO 面向接口传输(字段可裁剪),Domain Model 面向业务逻辑(包含行为和校验)
  • Controller 不写业务逻辑,Service 不操作 HTTP 对象,Repository 不包含业务规则

一条请求落到代码里时,常见流转是:

Middleware 做通用处理 → Controller 解析请求 → Service 编排业务 → Repository 读写数据

1.5 · 4. 数据如何存储与读取#

  • S: 应用需要持久化和查询结构化数据
  • C: 早期程序直接操作文件,数据格式与程序逻辑紧耦合,换程序就要重写解析
  • Q: 如何让数据独立于程序存在并高效访问?
  • A: 关系数据库:逻辑数据模型和物理存储分离

1.5.1 · RDBMS#

存储层:数据以固定大小的 page(通常 4KB/8KB)为单位落盘,page 组成 heap file;B-Tree 索引在 page 之上建立有序结构,避免全表扫描。

SQL 执行流程

SQL 文本 → Parsing(语法树)→ Semantic Analysis(表/列是否存在)
        → Optimizer(选择执行计划)→ Execution(实际执行)

事务与并发

  • ACID:原子性、一致性、隔离性、持久性
  • WAL(Write-Ahead Log):先写日志再改数据页,崩溃后可通过日志恢复;后台线程异步刷脏页到磁盘
  • MVCC:每行数据维护多个版本,读操作看到事务开始时的快照,写操作创建新版本,读写互不阻塞

1.5.2 · NoSQL#

不同场景下权衡”人怎么理解数据”和”机器怎么存储数据”:

类型代表适用场景数据模型
文档型MongoDB结构灵活、嵌套数据JSON/BSON 文档
列族Cassandra、HBase高写入吞吐、时间序列行键 + 列族
图数据库Neo4j关系密集、多跳查询节点 + 边
时序数据库InfluxDB、TimescaleDB监控、IoT、指标时间戳 + 值

这里的核心不是”会不会写 SQL”,而是理解:

  • 数据模型如何设计
  • 查询为什么快或慢
  • 并发读写时如何保证正确性

1.6 · 5. 热点数据如何加速#

  • S: 部分数据被频繁读取,每次都查数据库性能浪费
  • C: 计算昂贵、数据量大、读多写少的场景下,直接查源数据太慢
  • Q: 如何用更快的存储加速热点数据访问?
  • A: 把部分主数据放到更快的存储里,用空间和一致性换性能

缓存无处不在:DNS 缓存、CPU cache、浏览器缓存、CDN、API 缓存、Redis/Memcached 内存缓存。

核心难题不是”会用 Redis”,而是:

  • 淘汰策略:LRU(最近最少使用)、LFU(最不频繁使用)、TTL(过期时间)
  • 一致性问题:缓存与数据库数据不一致时如何处理

1.6.1 · 缓存模式

模式写流程读流程一致性适用场景
Write-Through写缓存,缓存同步写 DB读缓存读写均匀、一致性要求高
Write-Back写缓存,缓存异步批量写 DB读缓存弱(可能丢数据)写密集、可容忍短暂不一致
Cache-Aside写 DB,删缓存缓存未命中时查 DB 并回填最终一致大多数业务场景最常用

缓存不是替代数据库,而是放在数据库前面,解决”热点数据访问太慢”的问题。代价是:

  • 需要接受一定程度的一致性复杂度
  • 需要设计失效、淘汰、回填策略

1.7 · 6. 慢任务如何剥离出主链路#

  • S: 某些逻辑(发邮件、生成报表、视频转码)耗时较长
  • C: 放在主请求链路里同步执行会阻塞响应,影响用户体验和系统吞吐
  • Q: 如何把慢任务从请求链路中剥离?
  • A: 把任务扔进队列,由消费者异步处理,配合重试、ack、死信队列等机制

1.7.1 · 任务分类

类型示例
一次性任务发送欢迎邮件、生成发票
定时任务每日报表、定期清理过期数据
链式/递归任务视频上传 → 转码 → 生成缩略图 → 通知用户
批处理任务批量导入用户、批量发送通知

1.7.2 · 设计原则

  • 任务要小:单个任务职责单一,便于重试和调试
  • 可观测:任务状态、耗时、失败原因需要可查
  • 可重试:失败后能自动重试,配合退避策略
  • 有限流:防止消费者被压垮
  • 幂等:同一任务执行多次结果一致,重试不会产生副作用

1.7.3 · 常见组件与选型

异步系统里常见的不只是”有个队列”,而是一组配套组件:

  • Producer:业务服务,负责投递任务/事件
  • Broker / Queue:保存消息,负责投递和削峰
  • Consumer / Worker:异步执行任务
  • Scheduler:投递延迟任务、定时任务
  • Retry / DLQ:失败重试、死信隔离
  • Result Store / Status Store:保存任务状态、执行结果
  • Monitoring:观察堆积长度、消费延迟、失败率

常见技术选型:

组件/技术更像什么常见场景特点
Redis + BullMQ / Celery任务队列发邮件、生成报表、图片处理上手快,适合业务任务调度
RabbitMQ可靠消息队列任务分发、工作队列、路由分发ack、重试、路由能力强
SQS / 云消息服务托管任务队列云上业务异步化运维成本低,和云生态集成强
Kafka事件流平台用户行为流、订单事件、日志采集、异步解耦吞吐高、可回放、适合事件驱动和数据管道

Kafka 值得单独提一下:它当然也能承接异步处理,但它更偏向事件流(event streaming),不是传统意义上”取一个任务、做完就删掉”的任务队列。

  • 如果需求是”某个慢操作后台执行掉”:更常见的是 Redis 队列、RabbitMQ、SQS
  • 如果需求是”一个事件发生后,多个系统都要订阅处理,而且希望保留历史、支持重放”:Kafka 更合适
  • 典型例子:订单已创建 事件发到 Kafka,库存、优惠券、推荐、数据平台都各自消费

1.7.4 · 死信队列(DLQ)#

死信队列(Dead Letter Queue)是专门用来存放无法被正常消费的消息/任务的队列。

常见进入死信的原因:

  • 消费失败超过最大重试次数
  • 消息过期仍未处理
  • 队列已满或消息被 broker 拒收
  • 消费者显式 reject/nack,且不再重新入队

它的目标不是立刻重试成功,而是先把异常消息从主流程中隔离出来,避免:

  • 坏消息反复重试,持续占用资源
  • 主队列被少数异常任务卡住
  • 故障消息和正常消息混在一起,难以排查

一个典型流程:

  1. 任务先进入主队列
  2. 消费者处理失败,系统按策略自动重试
  3. 超过重试上限后,消息被转移到 DLQ
  4. 开发或运维再分析是数据问题、代码 bug,还是下游依赖故障

1.7.5 · 按环境的常见组件组合

环境常见组件目标
开发环境(dev)本地应用 + 本地 DB + 本地 Redis;必要时用 docker-compose 跑 RabbitMQ / Kafka快速启动、便于调试
测试/预发环境(test/staging)与生产同类组件,但规模更小;保留队列、缓存、定时任务、监控尽量模拟真实链路,提前暴露集成问题
生产环境(prod)高可用 DB、Redis、消息队列/Kafka、Scheduler、DLQ、告警监控、日志系统稳定性、可恢复性、可观测性

一个比较常见的落地方式:

  • 小型项目/单体服务:Postgres + Redis + BullMQ/Celery
  • 中型业务系统:Postgres/MySQL + Redis + RabbitMQ
  • 事件驱动/数据量大:OLTP 数据库 + Redis + Kafka + 独立消费者集群

所以这节可以记成一句话:异步任务常用队列,跨系统事件流常提 Kafka;环境越往生产走,越强调高可用、死信、监控和调度。

1.8 · 7. 把整条链路串起来#

把前面的内容连起来,一个常见的后端请求是这样工作的:

  1. 浏览器发起 HTTP request
  2. 请求经过网络到达服务,路由命中某个接口
  3. Middleware 做日志、鉴权、参数校验
  4. Controller 把请求转换成 DTO,交给 Service
  5. Service 执行业务逻辑,必要时读写数据库
  6. 如果数据是热点,先查缓存,未命中再查 DB 并回填
  7. 如果某些步骤很慢,就把任务投递到队列,由 worker 异步处理
  8. 最终系统返回响应,并通过监控、日志、告警观察整个过程

这也是后端系统设计的基本思路:

  • 主链路尽量短
  • 慢操作尽量异步
  • 热点数据尽量缓存
  • 权限边界尽量清晰
  • 数据读写尽量可预测、可恢复、可观测