前后端黑话图鉴

这些词你一定在文档、code review、技术讨论里见过——但真正说清楚的机会不多。现在用大白话 + 场景试着帮你理解它们。

前后端黑话图鉴
Photo by Mohammad Rahmani / Unsplash

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

工程上的很多设计决策,都是在这些问题的答案之间做权衡。