前后端黑话图鉴
这些词你一定在文档、code review、技术讨论里见过——但真正说清楚的机会不多。现在用大白话 + 场景试着帮你理解它们。
1. 幂等性(Idempotency)
一句话:不管执行一次还是多次,结果都一样。
网络是不可靠的。用户点了"付款",请求超时了——但服务器其实已经扣款,只是响应没送达。前端不知情,用户再点一次,就多扣了一次。
幂等性的解法是给请求附带一个唯一标识(Idempotency-Key,通常是 UUID),服务端缓存这个 key 对应的结果。重复请求来了,直接返回缓存结果,不再执行操作。
HTTP 方法里,GET / PUT / DELETE 天然幂等,POST 不是,需要额外保障。
2. 竞态条件(Race Condition)
一句话:多个操作同时进行,结果取决于谁先完成,而这个顺序是不确定的。
前端场景:搜索框快速输入,发出请求 A(搜"苹果")和请求 B(搜"香蕉")。B 先发出但 A 后返回,界面最终显示了旧的苹果结果——B 的正确结果被覆盖了。
解法:用 AbortController 在发新请求前取消旧请求;或给请求打序号,只渲染最新一次的响应。
后端场景:库存只剩 1 件,两个用户同时下单,都读到"有货",都通过校验,各自扣减,库存变成 -1(超卖)。
解法:乐观锁(更新时校验版本号)或悲观锁(直接锁行,让请求排队)。
3. 防抖与节流(Debounce & Throttle)
一句话:限制高频触发的函数,减少不必要的执行次数。两者目的相同,策略不同。
防抖(Debounce)
等用户"停下来"再执行。每次触发都重置计时器,只有连续操作结束后的一段静默期过去,才真正执行一次。
适合:搜索框输入联想(用户还在打字时不发请求)、窗口 resize 后重排布局。
节流(Throttle)
无论触发多频繁,保证固定间隔最多执行一次。
适合:滚动加载(每隔 200ms 检查一次位置)、按钮防连点、游戏里的技能冷却。
| 防抖 | 节流 | |
|---|---|---|
| 核心逻辑 | 重置计时器 | 固定间隔 |
| 执行时机 | 停止触发后 | 均匀分布 |
| 典型场景 | 搜索联想 | 滚动事件 |
4. 乐观锁 vs 悲观锁(Optimistic Lock vs Pessimistic Lock)
一句话:两种处理并发冲突的哲学——乐观的假设"大概率没人跟我抢",悲观的假设"一定有人跟我抢"。
乐观锁
不加锁,更新时带上版本号(version)。写入前检查版本是否还和读取时一致,不一致说明被别人改过,拒绝写入,让调用方重试。
- 优点:并发高、无锁等待
- 缺点:冲突频繁时重试次数多,性能反而差
悲观锁
操作前先锁住资源(SELECT ... FOR UPDATE),别人想操作就得排队等。
- 优点:冲突必然解决,结果可预期
- 缺点:锁的粒度如果太大,性能瓶颈明显,还可能造成死锁
实际选择:读多写少、冲突概率低 → 乐观锁;写操作频繁、强一致性要求 → 悲观锁。
5. 缓存三兄弟:穿透、击穿、雪崩
这三个词专门描述缓存层(Redis 等)在不同情况下被"打穿"的问题。
缓存穿透(Cache Penetration)
查询一个根本不存在的数据。缓存里没有,每次都打到数据库,数据库也查不到——恶意攻击者可以用大量不存在的 key 把数据库打垮。
解法:缓存空值(查不到也缓存一个空结果,设短过期时间);或用布隆过滤器(Bloom Filter)在入口快速判断 key 是否可能存在。
缓存击穿(Cache Breakdown)
一个热点 key 恰好在某一刻过期,大量并发请求同时穿透到数据库,把它打垮。
解法:热点 key 不设过期时间(后台定时刷新);或用互斥锁,让第一个请求去数据库查,其余请求等待。
缓存雪崩(Cache Avalanche)
大量 key 同时过期(比如缓存集中在同一时间设置),或缓存服务宕机,请求全部打到数据库,引发雪崩式崩溃。
解法:给过期时间加随机偏移(不让 key 同时过期);缓存集群高可用;限流降级兜底。
6. 死锁(Deadlock)
一句话:两个(或多个)操作互相等待对方释放资源,谁也无法继续执行,永久僵住。
经典场景:
- 事务 A 锁住了表 1,等表 2
- 事务 B 锁住了表 2,等表 1
- 两者互相等待,永远等下去
解法:
- 规定加锁顺序:所有操作按同一顺序申请锁(比如永远先锁表 1 再锁表 2),就不会形成环
- 设置超时:等锁超过一定时间就放弃并回滚
- 数据库检测:MySQL、PostgreSQL 等会自动检测死锁并主动杀掉一个事务
7. 事务的 ACID
一句话:数据库保证操作可靠性的四个属性,缺一不可。
| 字母 | 属性 | 含义 |
|---|---|---|
| A | 原子性(Atomicity) | 要么全做,要么全不做,不存在"做了一半" |
| C | 一致性(Consistency) | 操作前后,数据库从一个合法状态变到另一个合法状态 |
| I | 隔离性(Isolation) | 并发事务之间互不干扰,就像串行执行 |
| D | 持久性(Durability) | 一旦提交,数据永久保存,宕机也不丢 |
隔离性最复杂,根据"隔离程度"分四个级别(从弱到强):读未提交 → 读已提交 → 可重复读 → 串行化。级别越高越安全,但并发性能越低。MySQL InnoDB 默认是"可重复读"。
8. 短路(Short Circuit)
一句话:条件判断发现"已经确定结果了",后面的条件不再执行。
在代码里,&& 和 || 都有短路行为:
// A 为 false,B 根本不会执行
if (A && B) { ... }
// A 为 true,B 根本不会执行
if (A || B) { ... }
这在前端非常常用——用来避免空指针:
// user 为 null 时,user.name 不会被访问,不会报错
const name = user && user.name;
// 现代写法,等价
const name = user?.name;
在微服务架构里,"短路"还有另一个含义:熔断器(Circuit Breaker)。当某个下游服务连续失败,熔断器"断开",后续请求直接返回错误或降级结果,不再尝试调用,避免雪崩。
9. 防重放攻击(Replay Attack Prevention)
一句话:攻击者截获一次合法请求,原样重发——系统如何识别并拒绝这种"旧请求"?
常见场景:截获一次合法的转账请求,反复发送,导致多次转账。
防御手段:
- 时间戳:请求携带时间戳,服务端拒绝超过 N 秒的请求
- Nonce(一次性随机数):每个请求带唯一随机数,服务端记录已用过的 nonce,相同 nonce 直接拒绝
- 两者结合:时间窗口内的 nonce 才需要记录,超出窗口直接靠时间戳拒绝,减少存储压力
防重放和幂等性是一对搭档:幂等性保证重试安全,防重放保证恶意重发无效。
10. 长轮询、短轮询、WebSocket、SSE
四种实现"服务器推送消息给客户端"的方式,从笨到优雅:
短轮询:客户端每隔几秒就问服务器"有新消息吗"。简单粗暴,浪费资源。
长轮询:客户端发请求,服务端憋住不回复,等有消息了再返回。返回后客户端立即再发一个新请求。比短轮询好,但仍有延迟,且每次都建立新连接。
SSE(Server-Sent Events):基于 HTTP,服务端建立一个"永久"的单向流,持续往客户端推数据。实现简单,天然支持断线重连,但只能服务端→客户端单向。适合:股票行情、日志流、AI 对话的流式输出(你看到 ChatGPT/Claude 逐字输出就是这个)。
WebSocket:全双工连接,客户端和服务端都能主动发消息。适合:在线聊天、多人协作、实时游戏。比 SSE 重,但真正双向。
| 实时性 | 双向 | 实现复杂度 | |
|---|---|---|---|
| 短轮询 | 差 | 是(但靠轮询) | 低 |
| 长轮询 | 中 | 是 | 中 |
| SSE | 高 | 单向 | 低 |
| WebSocket | 高 | 是 | 高 |
这些概念听起来高深,本质都是在回答几个朴素的问题:
- 重试安全吗? → 幂等性
- 并发会出错吗? → 竞态、乐观/悲观锁、ACID
- 缓存会被打穿吗? → 穿透、击穿、雪崩
- 高频操作怎么控制? → 防抖、节流
- 消息能实时到达吗? → 轮询、WebSocket、SSE
工程上的很多设计决策,都是在这些问题的答案之间做权衡。