10

我有以下用例:多个客户端推送到共享的 Redis 列表。一个单独的工作进程应该耗尽这个列表(进程和删除)。等待/多执行以确保顺利进行。

出于性能原因,我不想立即调用“drain”进程,而是在 x 毫秒后,从第一个客户端推送到(然后为空)列表的那一刻开始。

这类似于分布式下划线/lodash debounce函数,定时器在第一个项目进入的那一刻开始运行(即:“领先”而不是“尾随”)

我正在寻找以容错方式可靠地做到这一点的最佳方法。

目前我倾向于以下方法:

  1. Redis SetNXandpx方法一起使用。这允许:
    • 仅将值(互斥锁)设置为专用键空间(如果尚不存在)。这就是nx参数的用途
    • 在 x 毫秒后使密钥过期。这就是px参数的用途
  2. 1如果可以设置该值,则此命令返回,这意味着以前不存在任何值。否则它会返回0。A1表示当前客户端是自 Redis 列表耗尽后第一个运行该进程的客户端。所以,
  3. 此客户端将作业放在计划在 x 毫秒内运行的分布式队列中。
  4. x 毫秒后,接收作业的工作人员开始排空列表的过程。

这在纸上有效,但感觉有点复杂。还有其他方法可以使这项工作以分布式容错方式工作吗?

顺便说一句:Redis 和分布式队列已经到位,所以我不认为在这个问题上使用它是一个额外的负担。

4

1 回答 1

7

很抱歉,但正常的反应需要一堆文字/理论。因为你的好问题你已经写了一个很好的答案:)

首先,我们应该定义术语。下划线/lodash 方面的“去抖动”应该在David Corbacho 的文章解释下学习:

Debounce:将其视为“将多个事件组合在一起”。想象一下你回家,进入电梯,门正在关上……突然你的邻居出现在大厅里并试图跳上电梯。要有礼貌!并为他打开门:你正在阻止电梯离开。考虑到第三个人可能会再次发生同样的情况,依此类推……可能会延迟出发几分钟。

节流阀:把它想象成一个阀门,它调节处决的流程。我们可以确定一个函数在特定时间内可以被调用的最大次数。所以在电梯的类比中,你足够礼貌地让人们进来 10 秒,但一旦延迟过去,你必须走!

您询问的debounce第一个元素将被推送到列表中:

所以,以电梯为例。电梯应在电梯到达第一人后 10 分钟后上升。不管有多少人挤进电梯里。

在分布式容错系统的情况下,这应该被视为一组要求:

  1. 新列表的处理必须在插入第一个元素(即创建列表)之后的 X 时间内开始。
  2. 工人崩溃不应该破坏任何东西。
  3. 无死锁。
  4. 无论工人的数量是多少 - 无论是 1 还是 N,都必须满足第一个要求。

即您应该知道(以分布式方式) - 一组工作人员必须等待,或者您可以开始处理列表。一旦我们说出“分布式”和“容错”这个短语。这些概念总是伴随着他们的朋友:

  1. 原子性(例如通过阻塞)
  2. 预订

在实践中

在实践中,恐怕你的系统需要稍微复杂一点(也许你只是没有写,而你已经有了)。

你的方法:

  1. 通过 SET NX PX 使用互斥锁进行悲观锁定。NX是保证一次只有一个进程在做这项工作(原子性)。确保如果此过程发生某些事情,PXRedis 会释放锁(死锁容错的一部分)。
  2. 所有工作人员都试图捕获一个互斥锁(每个列表键),所以只有一个人很高兴并且会在 X 次之后处理列表。这个过程可以更新互斥体的 TTL(如果需要更多的时间)。如果进程崩溃 - 互斥锁将在 TTL 后解锁并与其他工作人员一起抓取。

我的建议

Redis 中的容错可靠队列处理围绕RPOPLPUSH 构建

  • RPOPLPUSH 项目从处理到特殊列表(每个工人每个列表)。
  • 处理项目
  • 从特殊列表中删除项目

要求因此,如果工人崩溃了,我们总是可以将损坏的消息从特殊列表返回到主列表。Redis 保证了 RPOPLPUSH/RPOP 的原子性。也就是只有问题组的工人等待一段时间。

然后是两个选项。首先 - 如果有很多客户和较少的工人在工人一侧使用锁定。因此,尝试将互斥锁锁定在 worker 中,如果成功 - 开始处理。

反之亦然。每次执行 LPUSH/RPUSH 时使用 SET NX PX(如果您有很多工作人员和一些推送客户端,则有“在弹出之前等待 N 时间”解决方案)。所以推是:

SET myListLock 1 PX 10000 NX 
LPUSH myList value

每个工作人员只需检查 myListLock 是否存在,我们应该至少等待关键 TTL,然后再设置处理互斥锁并开始耗尽。

于 2015-12-24T14:20:19.410 回答