menuTicketing

工作原理


锁的获取与释放

客户端获取(A)一个 key 时,服务器会以以下三种方式之一响应。

key 状态服务器动作响应
未注册立即注册(过期时间 = now + lease),签发令牌A + token + key
已注册 + 已过期续期(now + lease),签发新令牌A + token + key
已注册 + 有效(占用中)加入等待队列,响应挂起释放/过期时返回 A,超过 wait 时返回 T
  • 释放按 FIFO 公平处理。 持有者释放(R)后,会直接移交给队列最前面的等待者 —— 无需重新竞争,立即连同新令牌一起转交。
  • lease 是安全网。 即便客户端崩溃或忘记释放,只要 lease 时间一到,服务器就会自动回收 该 key 并转交给下一个等待者。因此除了正常的释放流程外,建议始终把 lease 设置得 足够宽裕,但又不至于在客户端崩溃时占用过久。
  • wait 是获取等待的上限。0 时无限等待,否则若在该时长(秒)内未能获得,服务器就会 放弃并以 T(超时)响应 —— 由于没有发生授予,因此不会造成锁泄漏。

围栏令牌

A 响应中携带的 token该次授予的单调递增 u64。每次获取都会签发一个比此前任何 令牌都更大的值。即便发生故障切换,新晋升为主节点的节点也会从复制到的最大令牌值之后继续 (承接)—— 因此即便放在整个集群范围内看,令牌也始终持续递增。

只要受保护的资源(账户、文件、订单等)只验证"当前令牌是否大于最后见过的令牌", 就可以拒绝那些在 lease 过期后才姗姗来迟、携带已失效的较低令牌进行访问的客户端。 正因如此,即便锁服务器在某一时刻并非完全一致,也能保证安全。

客户端的行为

各官方客户端共同遵循以下行为(各语言的具体 API 细节请参阅)。

  • 每个地址各维持一条持久连接,并在后台进行管理。若只提供一个地址,内部会对 同一节点维持两条连接,即便其中一条短暂断开,服务也不会中断。
  • 以轮询方式挑选连接。 断开的节点会被自动排除,并每隔 3 秒在后台 尝试重连。
  • 请求采用流水线(pipelining)方式发送。 无需等待响应即可发送下一个请求,响应顺序也可以与 请求顺序不同 —— 客户端通过回显的(op, key)组合来区分某个响应对应哪一个请求。若存在多个 相同 (op, key) 组合的请求,则按发送顺序依次匹配。
  • 获取尝试过程中若连接断开,会自动切换到下一条连接。 尝试了与已注册连接数相同的次数后仍没有 可用连接时,才会以错误形式通知调用方。
  • 释放操作会在 5 秒内以 200ms 的间隔重试 —— 这样即便释放的那一刻连接恰好断开,也不会让锁 一直滞留在服务器上(虽然最终 lease 会将其回收,但这样能更快地转交给其他等待者)。

集群 —— 基于优先级的无停机机制

  • peers 列表的顺序即为晋升优先级(排在最前面的优先级最高)。存活节点中优先级最高的 那一个成为主节点,其余节点则作为实时接收其状态复制数据的备用节点
  • 客户端请求只由主节点处理。连接到备用节点的客户端会被引导(M)至主节点地址 并转移过去。
  • 主节点故障/重启 → 备用节点晋升接替。
  • 优雅退出Ctrl+C)时,主节点会先将晋升权移交给继任者(交接),再重定向客户端, 从而将主节点空窗期降到最低。
  • 自动降级:当网络分区等原因导致两个节点同时成为主节点时,优先级较低的一方会 察觉到对方的存在,主动降级为备用节点(防止永久性脑裂)。

这种晋升方式并非基于法定人数(多数投票)。因此节点数量是奇数还是偶数并不重要,只要有 一台存活,服务就能维持

服务器数量无停机可容忍的同时故障数备注
1 台最快。重启时会有短暂中断
2 台1 台无停机的最小配置。 大多数场景下已足够
3 台2 台一台维护时仍保持冗余
4 台以上N−1 台只会增加传播开销 —— 不推荐

关于一致性需要了解的事

由于基于优先级的晋升不是共识机制,在网络分区发生的瞬间,两侧可能会短暂同时成为主节点, 且由于异步复制的特性,在故障切换的瞬间可能会丢失部分授予(grant)。也就是说,在故障切换/分区 期间,互斥并不能得到 100% 保证。 如果需要强保证,请让受保护资源按前文所述实现对 围栏令牌的验证 —— 只要拒绝过旧(较小)的令牌,即便锁服务器并非完全一致,也能保证安全。