拒绝迟到配送员的完成标记
一句话总结
租约是暂时接管的权利,先占令牌则用来区分这是第几轮的权利。过期的工作者即使活着回来,也不应该能够修改新工作者的记录。
为什么需要它
投递方 A 领走零食订单后停住了。如果把订单从队列中彻底删除,只留在 A 的内存里,订单也会随之消失。反过来,如果让它仍然在队列中可见,B 也可能同时领走。因此要保存 leased 状态,并加上标记,表示在一段时间内交由 A 负责。时间到了而 A 仍未完成,其他工作者就可以重新领取。
问题在于,过期并不会让 A 终止。如果 A 只是在长时间等待网络响应,那么在 B 重新先占之后,A 也可能收到成功响应。此时如果 A 仅以 id 为条件标记完成,就会抢走 B 当前的工作。只检查正在执行的人的名字也不够,因为同名的工作者重启后,无法区分上一次运行和新的运行。
工作原理
jobs 表的行中有 owner、lease_until、token 和 attempts。claim 成功时,attempts 和 token 各加 1。owner 是便于说明的工作者名称,token 是用来区分该业务先占代次的单调递增编号。一个 claim 结果中包含 id、qty、owner、token、attempt 和 lease_until。只有在收到的标记与当前行完全一致,并且租约和业务截止时间都还有剩余时,finish 才会反映结果。
0ms A가 선점: token=1, lease_until=1000
1000ms B가 재선점: token=2
1001ms A가 token=1로 완료 요청 → False, 현재 상태 유지
该代码块中的韩文注释依次说明:0ms 时 A 先占,得到 token=1、lease_until=1000;1000ms 时 B 重新先占,token 变为 2;1001ms 时 A 以 token=1 请求完成,返回 False,当前状态保持不变。
边界上,只有 now < lease_until 时租约才有效。如果 now 等于到期时刻,权利就已经结束。相反,新的先占可以领取 lease_until <= now 的任务。如果对这两个条件做出不同的解释,就会出现同一瞬间两个人都认为自己有效,或者没有任何人能接管的空档。测试会分别传入到期前、恰好到期时刻和到期后。
先占把查询和更新绑定在同一个写事务中。如果先读取可用的任务、之后才开始事务,A 和 B 就可能读到同一个 pending 行。这个队列用 SQLite 的短 BEGIN IMMEDIATE 事务,把选择和令牌更新串行化。由于这是一个没有理由长时间保持读取时点快照的任务队列,所以使用 rollback journal。这与前面实验中导出 WAL 快照所要求的并发形态不同。
先占提交之后,要释放锁再发送。如果在 DB 事务内一直等到网络响应,就连与这个任务无关的新受理和其他工作者的先占也会被阻塞。测试会让一个单独的连接在 send 回调内真正受理另一个业务,以确认锁是否还残留。评分的对象是:“不做外部操作的短事务”这一意图,在实际运行中是否得到遵守。
在现场相遇的样子
SQS 的 visibility timeout 也是相关概念,它会让收到的消息在一段时间内对其他消费者不可见。不过该产品的投递保证与这次本地 SQLite 队列的契约并不相同。本实验既不实现也不调用 SQS。官方说明中也指出,visibility timeout 并不能完全消除重复投递的可能性,所以接收端业务的幂等性需要另外考虑。
这里的先占令牌用于拒绝队列内过期的完成标记。并没有实现由接收服务器检查令牌的外部资源 fencing。如果 A 的请求已经到达接收服务器,即使队列拒绝了 A 的 finish,其外部效果也不会被撤回。因此在综合测试中,接收服务器会同时记录业务 ID 和数量,对相同 ID 的重发不再重复累加效果。这就是为什么队列的所有权与接收效果这两个边界各自都需要。
如果租约给得很长,重新交给他人处理已死工作者的任务的时间就会变长;如果给得很短,则会不必要地重发仍然正常的工作者的任务。这次的实现不会构建用于续约的 heartbeat。运行长时间任务时,需要把处理时间、续约周期、安全取消和接收端幂等性一起设计。仅仅把 lease_ms 调大并不是完整的解决方案。
也不会让租约比原来的业务截止时间更长。例如 deadline=100,而 now=0、lease_ms=1000,则实际的 lease_until 是 100。队列中保存的租约不应与业务预算相矛盾。尝试次数已耗尽且已过期的业务,要在发放新令牌之前移入隔离;已经持有有效租约的任务,不会仅因反复查询就被交给其他工作者。
下一项检查要做什么
在接下来的测验中,要判断名称、业务 ID 和先占编号各自区分的对象。在之后的综合实验中,会同时启动两个真实的子进程,查看先占结果是否只有一个。完成边界的失败消息中会同时给出当前结果和期望结果,并确认即使用相同的工作者名称重新先占,之前的令牌也会被拒绝。
参考:SQLite 事务、SQS visibility timeout。数字令牌并不是访问权限或认证手段。