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

Ticketing Java / Kotlin 库

GitHub Maven Central

单个 TicketBroker 同时提供三种调用风格 —— Kotlin 协程使用 acquire, Java 异步使用 acquireAsync(CompletableFuture),Java 阻塞式使用 acquireBlocking。无论用哪一种,底层都是同一个 broker、同一组连接。

仓库

xml
kts

示例

基础示例

应用启动时创建一个broker并共享。wait=0是不入队的一次立即尝试,最大255秒;lease为1–250秒。最后的minimum-work budget为必填,可为0。客户端将不足一秒的值向上取整,并在发送前拒绝越界值或大于规范化lease的budget,绝不clamp。

Kotlin (coroutines):

kotlin
val broker = TicketBroker.connect("127.0.0.1:5225")
broker.waitReady(Duration.ofSeconds(5))

val ticket = broker.acquire("key", Duration.ofSeconds(5), Duration.ofSeconds(30), Duration.ofSeconds(2))
val token = ticket.token
// In the same DB transaction: verify/update token high-water and perform the business write.
ticket.release()

Java (blocking):

java
TicketBroker broker = TicketBroker.connect("127.0.0.1:5225");
broker.waitReadyBlocking(Duration.ofSeconds(5));

try (Ticket ticket = broker.acquireBlocking(
        "key", Duration.ofSeconds(5), Duration.ofSeconds(30), Duration.ofSeconds(2))) {
    long token = ticket.getToken();
    // In the same DB transaction: verify/update token high-water and perform the business write.
}

自动close/drop只是bounded best-effort release;需要确认结果时使用explicit release API。token必须在同一DB transaction中fence受保护写入。

需要了解的行为

  • 只要request可能已发送一个byte而丢失确定响应,结果就是Indeterminate。客户端不以相同owner自动重发A,caller不得进入critical section。
  • 发送前cancel为unsent;possible-send后cancel关闭session。若并发parse到grant token,则尝试bounded exact-token补偿release。
  • M、所有E、malformed/oversized/unknown response均为session-fatal;未确定的possible-send acquire为Indeterminate。
  • B是capacity的确定拒绝并立即返回。内部不retry;caller可在application backoff后用新owner开始新acquire。
  • explicit/compensating release仅在call/enqueue起绝对5秒内重试exact token。R成功,N表示已不存在或不是current token;没有最终响应是error,不能推定成功。
  • 只有保守剩余时间为正且足够required work budget时才返回Ticket。超过250秒的work在发送前unsupported。
  • DB fencing强制使用token:在同一DB transaction中拒绝token <= stored_high_water,更新high-water并执行business write,commit/rollback结束后release。

安全选项(Token · TLS)

所有选项都是可选的。token 应与服务器的 client_tokens 一致,TLS 有四种模式: 关闭 / 系统信任存储 / 指定 CA / 跳过校验(仅测试用)。

kotlin
val broker = TicketBroker.builder()
    .addrs("10.0.0.1:5225", "10.0.0.2:5225", "10.0.0.3:5225")
    .token("123")
    .tls(TlsMode.SystemRoots)
    // .tls(TlsMode.Ca("ca.crt"))
    // .tls(TlsMode.InsecureSkipVerify)
    .connect()

在 Java 中,使用静态工厂方法 TlsMode.systemRoots() / TlsMode.ca("ca.crt") / TlsMode.insecureSkipVerify()

服务器端的令牌、TLS、集群配置可以在 Ticketing 服务器部署页面生成。

Spring 虚拟线程

在虚拟线程上运行 Spring MVC 时,Java 和 Kotlin 都使用 *Blocking 方法。 非 suspend 的控制器无法调用 acquire(suspend),因此在 Kotlin 中 acquireBlocking 同样是正常路径。它们不经过协程,只在等待响应时 park, 因此不会占用载体线程。

kotlin
@RestController
class OrderController(private val broker: TicketBroker) {

    @PostMapping("/orders/{id}")
    fun place(@PathVariable id: String): String {
        broker.acquireBlocking("order-$id", Duration.ofSeconds(5), Duration.ofSeconds(30)).use {
            // 临界区
        }
        return "ok"
    }
}

close()(try-with-resources / use)触发的后台释放默认运行在虚拟线程执行器上, 不会堆积在固定大小的线程池后面。若要改用 Spring 管理的执行器,可以这样传入。

kotlin
TicketBroker.builder()
    .addrs("127.0.0.1:5225")
    .executor(applicationTaskExecutor)
    .connect()

使用 WebFlux 或协程控制器时,继续使用 suspend 方法即可。