字段约束与取值范围
请求字段 (C→S)
| 字段 | 类型 | 有效范围 | 含义 | 违反时 |
|---|---|---|---|---|
op | ASCII 1B | A(0x41) · R(0x52) | 请求类型 | E bad_op |
wait | u8 | 0~255(秒) | 0为不入队的立即尝试;1..255为获取等待上限 | —(类型上始终有效) |
lease | u8 | 1~250(秒) | 租约;0与251..255拒绝/reserved | E bad_lease |
owner | u64 BE | 0 ~ 2^64-1 全域 | 获取尝试的标识符(由客户端生成) | ——(服务器不校验) |
token | u64 BE | 服务器发放的值 | 指定释放对象(仅 R) | 不一致时 N |
key | UTF-8 | 1 ~ 128 字节 | 锁名。不得包含空白(0x20)或换行(0x0A) | E bad_key |
- 不存在无限wait/lease。官方客户端会将不足一秒的值向上取整并检查范围;超出范围时不会clamp或发送,而是在发送前返回明确的输入错误。默认lease为30秒。
- 官方client API中的
wait是从调用开始,经local queue、确定未发送的连接重试、write直到响应的整个acquire上限。客户端只创建一次monotonic operation deadline;writer在实际发送前一刻将剩余秒数向上取整为u8并写入frame的wait。因此,即使连接恢复较晚或发生确定未发送的failover,server wait也不会从头重新开始。到deadline时仍在queue中的请求以确定未发送的timeout结束;若哪怕一个byte可能已经发出,则关闭对应的准确session并返回Indeterminate。只有原始wait=0使用wire0,这种立即尝试也有单独的有限I/O deadline,不会无限等待卡住的transport。但若deadline前已关联的A直到Ticket交付gate才被发现,因为server waiter已经结束,无需关闭session;应对已知exact token执行补偿release并以Indeterminate结束。已关联的T/B仍是确定未获取结果。 - 对原始
wait=0,wire与server端行为仍是一次不入队的立即尝试。在其单独的5秒transport deadline内,只有确定未发送时才可使用同一owner重新选择连接并retry。一旦哪怕一个byte可能已发送,就绝不重发acquire。 - 官方客户端API必须接收wire中不存在的
min_work_budget。它应覆盖critical work + 预计pause + DB commit/rollback完成,范围为0..250秒且不大于规范化后的lease;否则发送前返回UnsupportedDuration/InsufficientLease类错误。 - key 长度按字节数而非字符数计算 —— 中文在 UTF-8 中每个字符 3 字节,因此最多 42 个字符。
owner只是响应关联ID,不是权限。重复值不会返回active token或续租。同一客户端并发in-flight请求仍须使用不同的非零值以避免响应歧义;本地counter变为0或wrap时,官方客户端fail-closed。- explicit release与已知token的补偿release从call/enqueue起只有绝对5秒,包括重连和重试。
R表示成功,N表示已消失或并非当前token;未观察到响应绝不推定成功。bounded补偿队列满时依赖lease expiry安全网。 bad_key同时涵盖空 key、超过 128 字节、包含空白或换行、以及非法 UTF-8 这几种情况。 官方客户端还额外保证绝不发送\r—— 以\r结尾的 key 会在无声无息中变成另一个 key。
响应字段 (S→C)
| 字段 | 类型 | 范围 | 出现于 |
|---|---|---|---|
token | u64 BE | 1 及以上,每次发放递增 | A(新发放) · R/N(回显请求) |
owner | u64 BE | 原样回显请求值 | A · T · B(回显请求) |
key | UTF-8 | 原样回显请求值(1~128B) | A · T · B · R · N |
addr | UTF-8 | host:port | M · L |
reason | ASCII | 固定的 8 种 | E |
服务器从不签发token 0。single模式使用当前process lifetime内的counter;cluster模式使用共识复制的counter并在overflow前fail-closed。不会声称wall clock或随机数能保证重启后的单调性。
定长头长度
op 之后的定长二进制区段。这些字节可能包含 0x0A,因此解析器必须在查找换行之前先 消费掉这些字节。
| 方向 | op | 定长头 | 构成 |
|---|---|---|---|
| C→S | A | 10 B | wait(1) + lease(1) + owner(8) |
| C→S | R | 8 B | token(8) |
| S→C | A | 16 B | token(8) + owner(8) |
| S→C | T · B | 8 B | owner(8) |
| S→C | R · N | 8 B | token(8) |
| S→C | M · L · E | 0 B | 无(op 之后直接是文本) |
字节不足则返回 E bad_request。
帧大小
| 项目 | 值 |
|---|---|
帧上限(不含 \n) | 192 字节 — 超出时 E line_too_long |
最大 A 请求 | 1 + 10 + 128 + 1 = 140 B |
最大 R 请求 | 1 + 8 + 128 + 1 = 138 B |
最大 A 响应 | 1 + 16 + 128 + 1 = 146 B |
空帧(仅 \n) | keep-alive — 服务器忽略 |
192B上限高于最大frame(146B),合规客户端不会触及。结构错误或oversized frame之后不再信任framing,并关闭连接。
认证握手
| 项目 | 值 |
|---|---|
| 哈希 | SHA-256(在 challenge 行中标明) |
| nonce | base64url 字符 8 个 |
| 响应摘要 | base64url 43 个字符(无填充) |
| 行上限 | 256 字节 |
| 时限 | 10 秒(含 TLS 协商)—— 超时则关闭连接 |
详细流程请参阅认证握手。
服务器端上限
客户端不能直接设置,但会影响行为。
| 项目 | 默认值 | 设置 | 超限时 |
|---|---|---|---|
| 每key waiter | 2048(硬上限16384) | MAX_WAITERS | B(busy) |
| 全部waiter | 16384(硬上限65536) | MAX_TOTAL_WAITERS | 对该acquire返回B |
| 并发client连接 | 1024(硬上限8192) | MAX_CONNECTIONS | 立即关闭连接 |
| 每连接积压reply | 256 | 编译期常量 | 关闭慢客户端连接 |
| 全局in-flight acquire | 4096 | 编译期常量 | 对该acquire返回B |
| 全局in-flight release | 512,独立lane | 编译期常量 | 有界等待至连接关闭 |
| cluster active key | 65536 | 编译期常量 | 新key acquire返回B |
read buffer(4096B)、reply batch(64)、已开始client frame的progress timeout(5秒)及single expiry sweep(5秒)均为编译期常量。cluster expiry使用校验token的deadline min-heap而非全量扫描。完整设置见配置(环境变量)。
全部acquire、新key与全部waiter的admission在hard limit的90%关闭,使用量低于75%后才重开。 因此达到hard limit前新acquire也可能返回B;release与Raft recovery的预留capacity独立。
违反 → 响应速查
| 情形 | 响应 | 连接 |
|---|---|---|
| 未知 op | E bad_op | 关闭 |
| 定长头不足 | E bad_request | 关闭 |
lease为0或251..255 | E bad_lease | 关闭 |
| key 为空 / 超过 128B / 含空白或换行 / 非 UTF-8 | E bad_key | 关闭 |
| 帧超过 192B | E line_too_long | 关闭 |
| 认证摘要不匹配 | E auth_failed | 关闭 |
| 等待队列饱和 | B + owner + key | 保持 |
| 释放 token 不匹配 | N + token + key | 保持 |
全部错误原因及连接处理请参阅响应 · 错误 E。