目前正在测试中:完成后将开放 GitHub 代码。

Ticketing 是什么?

Ticketing 是一个专为分布式锁这一件事而生的专用锁服务器。它由一个 Rust 编写的 服务器二进制文件,以及按字节完全一致地实现同一二进制协议的各语言官方客户端组成。 只有获取(acquire)和释放(release)两个操作、按 key 划分的 FIFO 队列、围栏令牌, 以及需要时才开启的 Raft 集群 —— 只包含分布式锁所需的一切,别无其他。

与基于存储实现的锁有何不同

生产环境中的大多数分布式锁都依赖 key-value 存储 —— 在 Redis 上运行 Lua 脚本或 Redlock,或者基于 ZooKeeper、etcd 的会话与租约实现锁。Redis 是一款出色的存储, 但它终究是被挪用于分布式锁场景的 key-value 存储,而非专为锁而生的系统, 这就带来了结构性的开销:通用协议的解析成本、通用数据结构的开销,始终如影随形。

Ticketing 从一开始就专为分布式锁设计,每一层都是围绕锁构建的。

  • 协议 —— 既非文本,也非通用序列化格式,而是定宽二进制字段。无需解析即可按 字节偏移直接读取,并支持流水线(pipelining),无需等待上一次响应即可发送下一次请求。
  • 服务器内部 —— 全部状态只是按 key 划分的 FIFO 队列加上一个租约计时器。 请求处理路径中甚至不会进行堆分配。
  • 运维 —— 启动一个服务器二进制文件(或容器),用几个环境变量配置即可完成。 不需要设计 schema,不需要额外的存储,也不需要任何与锁无关的运维知识。

性能

  • 每秒处理超过 100 万次请求,内存占用不到 10MB
与最广泛使用的分布式锁系统 Redisson(Redis)的速度对比。
Ticketing 客户端Redis(Redisson RLock)
JavaScript
210.2 ms · 190,295 ops/s
210.2 ms
Rust
225.9 ms · 177,069 ops/s
225.9 ms
Go
228.4 ms · 175,131 ops/s
228.4 ms
C#
286.9 ms · 139,421 ops/s
286.9 ms
Kotlin
443.5 ms · 90,192 ops/s
443.5 ms
Java
457.8 ms · 87,374 ops/s
457.8 ms
C/C++
659.7 ms · 60,634 ops/s
659.7 ms
Python
739.4 ms · 54,098 ops/s
739.4 ms
Ruby
1,682.4 ms · 23,776 ops/s
1,682.4 ms
Redis基准
4,909.1 ms · 8,148 ops/s
4,909.1 ms
柱状长度为整个流程耗费的时间(毫秒),越短越快。
32 contended keys × 1,250 ops · concurrency 64 · mac mini m4 2024 basic (10 core), single local server, acknowledged release, 1s min-work budget

为什么选择 Ticketing

🚦 顺序完整性 —— FIFO 队列与直接移交

先到先得。持有者释放锁时,锁会直接移交给队列最前面的等待者,无需重新竞争。 不会因轮询重试式的锁竞争而产生饥饿(starvation),也不会出现重试风暴。

🛟 客户端挂掉也不会让锁卡死 —— lease

如果持有锁的进程挂掉或连接断开,服务器会在获取时设定的 lease 时长到期后 自动回收该锁,并移交给下一个等待者。忘记释放也不会导致整个系统停摆。

🔑 防止乱序操作 —— 围栏令牌

仅靠锁本身无法阻止一种事故:持有者在 lease 已过期后才迟迟醒来, 却毫不知情地请求完成工作。Ticketing 在每次获取时都会签发单调递增的围栏令牌。 只要受保护的资源坚持一条规则 —— "拒绝任何低于已见过的最新令牌" —— 就能堵住这最后的漏洞。详细原理见工作原理

🗳️ 基于 Raft 的无中断集群

单台服务器已经足够使用,但如果需要零停机,可以组成 3 台(或 5 台)的集群。 每一次锁状态变更都只有在过半数节点通过 Raft 共识达成一致后才会生效, 因此即使 leader 挂掉或网络发生分区,同一把锁也绝不会被同时发放两次。 leader 故障时,新 leader 会在约 1 秒内选出,客户端会自动切换过去。 即使节点像云实例重启或滚动维护那样逐个下线,服务也能持续运行。

🔒 内置认证与 TLS

每个连接在建立后都会立即完成 challenge-response 令牌认证,令牌不会以明文 形式出现在线路上。需要时可以用 TLS 加密整个连接。

🌐 跨所有主流语言,字节级一致的协议

Rust、Java/Kotlin、JavaScript/TypeScript、Python、C#、Go、Ruby、C/C++ —— 每个官方客户端都实现了同一套二进制协议,并为各语言生态提供自然贴合的 API (异步或阻塞式)。无论从哪种语言获取的锁,都会与其他语言的客户端在同一个 队列中公平排队。安装方法与示例见库列表

只有两个操作

作为专用锁服务器,它的接口面同样极简。

  1. 为想要保护的资源附加一个 key,例如 order-1234account-77daily-batch
  2. 在开始工作前获取(acquire)该 key —— 如果有人正在使用,就在 FIFO 队列中 排队等待。
  3. 工作完成后释放(release)该 key —— 下一个等待者会立即接手,无需重新竞争。

在这两个操作之上,唯二的选项就是 lease(自动回收)和 waitTimeout (放弃等待)—— 这就是全部 API。

接下来读什么