【后端架构】高并发系统设计宏观指南

49 阅读5分钟

应对高并发场景的设计,本质上是在做两件事:“保正确性”(数据绝对不能乱)和 “保性能”(服务绝对不能崩)。

本文将从这两个核心维度出发,总结常见的技术方案与业务场景。


一、 保正确性 (Safety)

无论并发多高,数据的准确性是底线。我们通过不同层级的“原子化操作”来守住这条线。

1. 数据库事务 (Transaction)

定义:将多条 SQL 语句打包成一个不可分割的整体。这个整体要么全部执行成功,要么全部不执行(回滚)。

  • 核心目标:解决多步操作的一致性问题。
  • 典型案例转账
    1. 从 A 账户扣 100 元。(执行成功 ✅)
    2. 给 B 账户加 100 元。(执行失败 ❌,比如网络突然断了,或者 B 账户状态异常)
    3. 结果:如果没有事务,A 的钱就凭空消失了。但有了事务,数据库会自动回滚 (Rollback) 第一步的操作,A 的余额恢复原状,仿佛什么都没发生过。
  • 代码特征:Prisma 的 Nested Writes (嵌套写入) 或 $transaction API。

2. 数据库原生原子操作

定义:数据库在执行单条 SQL 语句时是原子的。一条 UPDATE 语句在执行过程中,绝对不会被其他请求打断。

  • 核心目标:解决单行数据竞争(Race Condition)问题。防止多个人同时修改同一条数据时,旧值覆盖新值。
  • 典型案例抢最后一张票(余票 = 1)。
    • 错误做法 (应用层计算)
      1. 用户 A 查余票:看到是 1。
      2. 用户 B 查余票:也看到是 1。(此时 A 还没写回)
      3. 用户 A 买票:把 1 改成 0,写回数据库。
      4. 用户 B 买票:把 1 改成 0,写回数据库。
      5. 结果:卖出了两张票,超卖了!
    • 正确做法 (原子操作)
      1. 用户 A 和 B 同时请求“余票减 1”。
      2. 数据库会让这两条指令排队执行
      3. A 执行:UPDATE Ticket SET stock = stock - 1 -> 余票变 0。
      4. B 执行:UPDATE Ticket SET stock = stock - 1 -> 余票变 -1 (业务报错拦截)。
    • SQLUPDATE Ticket SET stock = stock - 1 WHERE id = 1

💡 进阶阅读:如果业务逻辑很复杂,无法用单条 SQL 解决(必须先查后改),请深入阅读 并发竞态与锁选型

3. Redis 原子操作

定义:Redis 的所有单个命令(如 DECR, SETNX)都是原子性的。因为 Redis 使用单线程处理命令,所有请求必须排队一个接一个执行,天然没有并发冲突。

  • 特性纯内存操作 + 单线程
  • 应用场景:具体流程见下文“保性能”章节

二、 保性能 (Performance)

解决了正确性后,面对海量流量,我们需要通过架构设计来保护脆弱的数据库。核心公式为:Redis (挡读) + MQ (缓冲写)

1. 读多写少 (Read-Heavy) —— 90% 的业务常态

这是互联网应用最常见的场景。

  • 典型案例
    • 商品详情页:几百万人浏览,只有运营偶尔修改价格/描述。
  • 技术方案Cache Aside Pattern (读写穿透)
    • :优先读 Redis。Hit 则返回;Miss 则查 DB 并回填 Redis。
    • :先更新 DB,然后删除 Redis(强制失效)。
    • 特点:数据以 DB 为主,Redis 只是 DB 的一个加速快照,挂了也没关系。

2. 读写双高 (High R+W) / 高并发写入

这是最复杂的场景,涵盖了日志上报(写极多)、弹幕评论(读写俱多)、大促下单(写洪峰)等。

  • 典型案例
    • 日志/监控上报:百万设备每秒上报状态。
    • 明星发微博:发的一瞬间有极大写入,同时有几亿人读取。
    • 热点直播间弹幕:在线人数多(读),互动频繁(写)。
    • 非秒杀下单:双11 普通商品的下单洪峰。
  • 技术方案MQ 全量入队 (写) + 多级缓存 (读)
    • :采用 全量入队 (Direct MQ)。请求来了不管三七二十一,先扔进 MQ。MQ 抗住瞬间的写入压力,后台 Consumer 以数据库能承受的速度慢慢批量写入 DB。
    • :靠 Redis 集群 甚至 CDN 抗压。
    • 核心策略:对于日志类(读少),Consumer 写库即可;对于微博类(读多),Consumer 写库的同时回写 Redis

3. 极端场景:秒杀 / 大促 (Flash Sale)

这是“僧多粥少”的特殊场景,核心目标是拦截无效流量

  • 典型案例
    • 抢 iPhone5 万台手机,500 万人抢。(注意:如果库存只有 10 台,直接写库即可,无需 MQ;MQ 是为了应对大库存高并发写入的场景)
    • 春晚摇一摇
  • 技术方案预热 + 过滤 + 异步
    • 阶段 1 (预热):活动开始前,将库存全量加载到 Redis。
    • 阶段 2 (过滤):用户请求先过 Redis。
      • 库存 < 0:直接返回“抢光了”。(拦截掉 99.9% 的流量,保护后端)
      • 库存 > 0:才有资格进入下一步。
    • 阶段 3 (缓冲):抢到资格的请求进入 MQ。
    • 阶段 4 (落地):Consumer 从 MQ 取出,慢慢写入数据库,生成订单。

总结

场景核心挑战核心解法数据流向
普通业务 / 高并发读数据库压力大 (读)Redis 缓存 (Cache Aside)App -> Redis (Miss -> DB)
高并发写数据库写不过来MQ 全量入队App -> MQ -> DB
秒杀/抢购流量巨大且无效Redis 过滤 + MQApp -> Redis (Filter) -> MQ -> DB