应对高并发场景的设计,本质上是在做两件事:“保正确性”(数据绝对不能乱)和 “保性能”(服务绝对不能崩)。
本文将从这两个核心维度出发,总结常见的技术方案与业务场景。
一、 保正确性 (Safety)
无论并发多高,数据的准确性是底线。我们通过不同层级的“原子化操作”来守住这条线。
1. 数据库事务 (Transaction)
定义:将多条 SQL 语句打包成一个不可分割的整体。这个整体要么全部执行成功,要么全部不执行(回滚)。
- 核心目标:解决多步操作的一致性问题。
- 典型案例:转账。
- 从 A 账户扣 100 元。(执行成功 ✅)
- 给 B 账户加 100 元。(执行失败 ❌,比如网络突然断了,或者 B 账户状态异常)
- 结果:如果没有事务,A 的钱就凭空消失了。但有了事务,数据库会自动回滚 (Rollback) 第一步的操作,A 的余额恢复原状,仿佛什么都没发生过。
- 代码特征:Prisma 的
Nested Writes(嵌套写入) 或$transactionAPI。
2. 数据库原生原子操作
定义:数据库在执行单条 SQL 语句时是原子的。一条 UPDATE 语句在执行过程中,绝对不会被其他请求打断。
- 核心目标:解决单行数据竞争(Race Condition)问题。防止多个人同时修改同一条数据时,旧值覆盖新值。
- 典型案例:抢最后一张票(余票 = 1)。
- 错误做法 (应用层计算):
- 用户 A 查余票:看到是 1。
- 用户 B 查余票:也看到是 1。(此时 A 还没写回)
- 用户 A 买票:把 1 改成 0,写回数据库。
- 用户 B 买票:也把 1 改成 0,写回数据库。
- 结果:卖出了两张票,超卖了!
- 正确做法 (原子操作):
- 用户 A 和 B 同时请求“余票减 1”。
- 数据库会让这两条指令排队执行。
- A 执行:
UPDATE Ticket SET stock = stock - 1-> 余票变 0。 - B 执行:
UPDATE Ticket SET stock = stock - 1-> 余票变 -1 (业务报错拦截)。
- SQL:
UPDATE 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)
这是“僧多粥少”的特殊场景,核心目标是拦截无效流量。
- 典型案例:
- 抢 iPhone:5 万台手机,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 过滤 + MQ | App -> Redis (Filter) -> MQ -> DB |