客户端获取(A)一个 key 时,服务器会以以下三种方式之一响应。
| key 状态 | 服务器动作 | 响应 |
|---|---|---|
| 未注册 | 立即注册(过期时间 = now + lease),签发令牌 | A + token + key |
| 已注册 + 已过期 | 续期(now + lease),签发新令牌 | A + token + key |
| 已注册 + 有效(占用中) | 加入等待队列,响应挂起 | 释放/过期时返回 A,超过 wait 时返回 T |
R)后,会直接移交给队列最前面的等待者 —— 无需重新竞争,立即连同新令牌一起转交。lease 是安全网。 即便客户端崩溃或忘记释放,只要 lease 时间一到,服务器就会自动回收 该 key 并转交给下一个等待者。因此除了正常的释放流程外,建议始终把 lease 设置得 足够宽裕,但又不至于在客户端崩溃时占用过久。wait 是获取等待的上限。 为 0 时无限等待,否则若在该时长(秒)内未能获得,服务器就会 放弃并以 T(超时)响应 —— 由于没有发生授予,因此不会造成锁泄漏。A 响应中携带的 token 是该次授予的单调递增 u64。每次获取都会签发一个比此前任何 令牌都更大的值。即便发生故障切换,新晋升为主节点的节点也会从复制到的最大令牌值之后继续 (承接)—— 因此即便放在整个集群范围内看,令牌也始终持续递增。
只要受保护的资源(账户、文件、订单等)只验证"当前令牌是否大于最后见过的令牌", 就可以拒绝那些在 lease 过期后才姗姗来迟、携带已失效的较低令牌进行访问的客户端。 正因如此,即便锁服务器在某一时刻并非完全一致,也能保证安全。
各官方客户端共同遵循以下行为(各语言的具体 API 细节请参阅库)。
lease 会将其回收,但这样能更快地转交给其他等待者)。peers 列表的顺序即为晋升优先级(排在最前面的优先级最高)。存活节点中优先级最高的 那一个成为主节点,其余节点则作为实时接收其状态复制数据的备用节点。M)至主节点地址 并转移过去。Ctrl+C)时,主节点会先将晋升权移交给继任者(交接),再重定向客户端, 从而将主节点空窗期降到最低。这种晋升方式并非基于法定人数(多数投票)。因此节点数量是奇数还是偶数并不重要,只要有 一台存活,服务就能维持。
| 服务器数量 | 无停机 | 可容忍的同时故障数 | 备注 |
|---|---|---|---|
| 1 台 | ✗ | — | 最快。重启时会有短暂中断 |
| 2 台 | ✓ | 1 台 | 无停机的最小配置。 大多数场景下已足够 |
| 3 台 | ✓ | 2 台 | 一台维护时仍保持冗余 |
| 4 台以上 | ✓ | N−1 台 | 只会增加传播开销 —— 不推荐 |
由于基于优先级的晋升不是共识机制,在网络分区发生的瞬间,两侧可能会短暂同时成为主节点, 且由于异步复制的特性,在故障切换的瞬间可能会丢失部分授予(grant)。也就是说,在故障切换/分区 期间,互斥并不能得到 100% 保证。 如果需要强保证,请让受保护资源按前文所述实现对 围栏令牌的验证 —— 只要拒绝过旧(较小)的令牌,即便锁服务器并非完全一致,也能保证安全。