前后端黑话图鉴

这些词你一定在文档、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

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

阅读更多

为了平凡的生活

为了平凡的生活

为了翻拣垃圾的孩子,和他们的梦想。 为了那些枯萎的树木, 为了那些如纸牌般倒塌的危楼。 为了那些安眠药, 和那些漫长而失眠的夜晚。 为了那些被困在原地的人, 为了改变那些腐朽而停滞的头脑。 为了那些被加速的人生, 为了恐慌,焦虑,和没有尽头的比较。 为了那些被迫接受的规则, 为了那些被强加的指令, 为了那些看不见的手, 我们成长, 成功, 不断向前。 加速学习, 加速工作, 加速赚钱, 加速消费, 加速体验, 加速衰老。 好像停下来, 就会落在后面。 我想慢下来。 想要一份普通的工作, 不用害怕失去。 想让孩子好好长大, 让老人慢慢变老。 想在傍晚回家, 吃一顿晚饭。 想让夜晚只是夜晚, 而不是焦虑、失眠和安眠药。 想让树长在那里, 而不是倒在推土机经过的地方。 想让房子能够站得住, 住进去,可以安心睡觉。 想让一个人活着, 不需要不停证明自己活得有价值。 我们曾经以为, 生活应该越来越精彩。 后来发现, 真正难得的是它能够保持正常。 没有突然坍塌的房子, 没有突然失去的工作, 没有突然降临的疾病,