Skip to content

锁 ​

simba.lock(name) 在一个步骤前后持有一个租约,并在之后释放它。锁是同一租约名上的一个选举:它取得第一个任期, 并在你完成后交还。

kotlin
simba.lock("invoice-42").acquire(Duration.ofSeconds(5)).use { term ->
    term.ensureValid(Duration.ofSeconds(2))
    invoiceStore.write(invoice, fencingToken = term.token)
}
java
try (LockTerm term = simba.lock("invoice-42").acquire(Duration.ofSeconds(5))) {
    term.ensureValid(Duration.ofSeconds(2));
    invoiceStore.write(invoice, term.getToken());
} catch (LockTimeoutException e) {
    // held elsewhere
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

获取锁 ​

方法行为
acquire(timeout)最多等待 timeout(按客户端时钟)以获得一个任期;未能及时赢得时抛出 LockTimeoutException。刚赢得就结束的任期不会结束等待:锁会继续竞争直到超时。超出纳秒表示范围的超时按 Long.MAX_VALUE 纳秒等待。中断调用方会放弃本次尝试并重新抛出 InterruptedException(对 Java 已声明)。在引擎循环上调用(只有直接执行的回调执行器才会走到)会抛出 IllegalStateException。
tryAcquire()只尝试一次:本客户端赢得第一个得到应答的尝试时返回任期;当另一个会话或本客户端持有该租约,或在一个租约时长内没有尝试得到应答(后端不可达)时返回 null。
acquireAsync(timeout)不阻塞的 acquire(5.1 起):返回 CompletionStage<LockTerm>,以任期完成;超时则以 LockTimeoutException、客户端关闭则以 IllegalStateException、超时为负数则以 IllegalArgumentException 异常完成;它本身不抛异常。
tryAcquireAsync()不阻塞的 tryAcquire(5.1 起):返回 CompletionStage<LockTerm?>,以任期或 null 完成。

不阻塞地获取 ​

acquireAsync 和 tryAcquireAsync 等待时不占用线程,在客户端的回调执行器上完成(与回调一样,只有使用直接执行的回调执行器时才会在事件循环上完成)。它的后续操作像回调一样运行:在其中对任期调用 close() 会释放但不等待。在 stage 完成之前就已结束的任期绝不会被返回:锁会继续竞争。取消返回的 future(toCompletableFuture().cancel(...))会放弃本次尝试,并释放已经赢得的任期,因此被取消的获取绝不会让锁一直被持有。 Reactor 用户可以用 Mono.fromCompletionStage 包装它。

回调执行器拒绝任务时,赢得的任期的 onElected 不会被投递(参阅 回调与生命周期):锁的选举会 退让一个租约时长,本次尝试看不到这个任期,因此会一直等待,直到超时或赢得之后的任期。

kotlin
simba.lock("invoice-42").acquireAsync(Duration.ofSeconds(5))
    .thenCompose { term ->
        invoiceStore.writeAsync(invoice, fencingToken = term.token)
            .whenComplete { _, _ -> term.releaseAsync() }
    }
java
simba.lock("invoice-42").acquireAsync(Duration.ofSeconds(5))
    .thenCompose(term -> invoiceStore.writeAsync(invoice, term.getToken())
        .whenComplete((result, error) -> term.releaseAsync()));

客户端之外的 DistributedLock 实现会继承默认方法:它们在虚拟线程上调用阻塞版本,取消时中断该线程。

协程 ​

simba-coroutines(5.1 起,依赖 kotlinx-coroutines-core;simba-core 不依赖它)把异步调用适配为协程:

kotlin
simba.lock("invoice-42").withLock(Duration.ofSeconds(5)) { term ->
    invoiceStore.write(invoice, fencingToken = term.token)
}
函数行为
DistributedLock.awaitAcquire(timeout)挂起直到赢得任期;超时抛出 LockTimeoutException。取消协程会放弃本次尝试,并释放已经赢得的任期。
DistributedLock.awaitTryAcquire()挂起版的 tryAcquireAsync:返回任期或 null。
DistributedLock.withLock(timeout) { term -> }获取锁、运行代码块,之后释放任期,失败或取消时也会释放。任期仍可能在代码块返回前结束:请把 term.token 传给受保护的资源。
Term.awaitRevoked()挂起直到任期被撤销,返回 RevokeReason;取消等待不影响任期本身。
SimbaEventFlow一个 SimbaEventListener,用 Simba.builder().listener(...) 注册;events 是热的 SharedFlow<SimbaEvent>,收集者落后超过 bufferCapacity 个事件时丢弃最旧的事件。

在一个客户端内,同一名称最多只有一个持有者:对同一名称的第二次 acquire 会等到第一次被释放(或它自己的超时到期)。 同名的锁和选举共享一个租约,因此不要把同一个名称同时用于两者。

LockTerm 就是凭证 ​

acquire 返回一个 LockTerm:它是一个 Term,同时还能释放租约。

  • 它不绑定线程:可以交给另一个线程或协程,并在那里关闭。
  • close() 撤销任期并释放租约,并等待其完成(在 leader 身份回调或 leader 任务内部除外)。releaseAsync() 做同样的事但不等待;它返回的 stage 与锁的获取结果一样在回调执行器上完成,因此在其后续操作里调用 close() 会立即返回。 两者都是幂等的;关闭一个释放已经完成的任期绝不会等待(在事件循环上也一样)。
  • token、isValid、remaining() 和 ensureValid() 的行为与任何任期相同。

提前失去锁 ​

当任期被撤销时,持有的锁会在你关闭它之前结束:会话的本地截止时刻已过、后端报告会话已过期,或另一个会话接管了租约。 此时锁被永久释放,不会在你不知情的情况下被重新获取:

  • term.isValid 变为 false,term.revoked 以原因完成;
  • 你的下一次 ensureValid() 抛出 TermExpiredException;
  • 之后再关闭该 LockTerm 是无害的空操作。

由于锁可能在你的代码仍在运行时结束,请把 term.token 传给受保护的资源,让它拒绝更低的 token;见 Fencing Token。在每个步骤之前调用 ensureValid(expectedStepDuration), 以免开始一个任期无法覆盖的步骤。

在锁和选举之间选择 ​

锁选举
生命周期围绕一个步骤,由你释放与选举运行时间一样长
提前结束时永久释放重新竞争下一个任期
典型用途对一个资源的互斥访问由一个节点持续扮演的角色

对于在一个节点上周期性或持续运行的任务,请使用 leader 任务,而不是在循环中加锁: 它只竞争一次,并在多次运行之间保持任期。

基于 Apache License 2.0 发布。