← 返回笔记

learning note

单体 Single 与分布式 Single 的区别

对比单 JVM 内 Single-flight 和分布式 Single-flight 在状态共享、结果回放、接管能力上的差异。

单体Single-flight缺陷:

设计缺陷:

在单机时代,一个请求进来,最多只是同一台机器上有几个线程同时处理;但到了真实生产环境,服务通常都挂在负载均衡(什么是负载均衡)后面,同一个用户因为重试、网络抖动、前端重复点击、消息重复投递、上游超时补偿等原因,可能会把语义上完全相同的请求同时打到多台机器上。

也就是说在负载均衡的背景下,你靠JVM单体的锁是完全锁不住的。仍然会存在对AI接口的大量重复浪费调用。

对于普通 CRUD接口来讲,这种重复执行最多是多查一次、多写一次;但对 AI 调用来说,代价就非常高,因为每一次调用大模型都意味着真实的耗时、算力和费用开销,而且还可能引发结果不一致、上下游状态重复推进、缓存污染等连锁问题。

分布式Single-Flight尝试在解决什么问题?

分布式Single-Flight本质上是在尝试解决:在集群环境下,如何识别出同一份语义请求,并确保同一时刻只有一个节点真正执行,其他节点等待并复用结果

这么说有点太装逼了,说人话其实就是:跨机器的请求去重和结果复用

从设计思路上来讲,我们会在Redis中维护一个状态机**:同一个语义请求下只有一个服务器节点会成为当前请求的Owner,然后执行请求以及共享结果。**

再进一步,我们还要处理Owner节点执行到一半挂掉,超时,网络抖动,结果回写竞争问题。因此一整个分布式Single-Flight不仅仅要做到只执行一次,还要做到节点失败可接管,结果写入无脏写,成功过后可复用

更高维度视角:

其实讲完上述的,兄弟们可以发现:其实我们的分布式Single-Flight本质上并不是一个简单的业务幂等或者结果缓存

它更关注的点是:当前这个请求在执行的时候,其他相同的请求应该怎么办?

所以这种分布式Single-Flight就很适合那种执行成本高、请求易重复、结果可复用、又部署在多实例集群里的场景。那这简直是完美适合我们的项目。

在我们这个项目中:面试评分、追问生成、简历抽题、神态分析这些 AI 请求都属于高成本操作,在负载均衡和多实例部署下,很容易因为重复触发而被多台机器同时执行**。分布式 Single-flight 就是为了把这些本质上是同一份 AI 请求的调用收敛成一次真正执行,其余请求只等待和复用,从而同时解决重复成本、一致性风险、故障接管和系统稳定性这几个问题。**** **

分布式系统的CAP理论:

?????????????????????????? 在分布式系统中,一致性、可用性和分区容错性三者不能同时完全满足,系统设计必须在其中做出权衡。

C (Consistency - 一致性): 每次读取要么获得最新写入的数据,要么报错。在分布式系统中,这意味着所有节点在同一时间的数据必须完全一致,就像访问单机单台数据库一样。

A (Availability - 可用性): 每次请求都能获得一个(非错误)的响应,但不保证获取的是最新写入的数据。系统必须一直处于“可提供服务”的状态。

P (Partition Tolerance - 分区容错性): 尽管网络会丢失或延迟节点之间的任意数量的消息(即发生网络分区),系统仍能继续运行。

所以,为了实现在分布式环境下,系统能够实现高可用,基于CAP理论,我们的分布式Single-flight分布式做了如下的设计

核心设计理念与 CAP 取舍:

  • 全局锁与状态机(偏向 CP): 使用 Redis Lua 脚本保证争抢执行权的原子性。全网多个实例同时发起相同请求时,系统强制保证只有一个 Leader 节点突围。此时为了防止“僵尸节点”脑裂,引入 Fencing Token(防护令牌) 机制,一旦原 Leader 假死超时,立即剥夺其回写结果的权利。保证了在并发控制上对强一致性(C)。

  • 心跳续期与主动接管(保证 A): 如果 Leader 节点真宕机了,其他处于等待共享状态的 Follower 节点不能被永远阻塞。通过 Owner Heartbeat 机制,一旦 Leader 心跳丢失,Follower 迅速超时并重新发起选举接管请求。这保证了哪怕发生单点故障,整个面试链路的可用性(A)依然不受影响。

  • 本地 L1 Cache 终极降级(AP 兜底): 当遭遇极端网络故障(严重的 P),Redis 集群整体不可用时,框架自动退化回本地单机 Single-flight 模式。此时系统主动放弃了全局一致的请求合并(C),但死保住了用户的核心面试流程不中断(A)。