0


我有一个难题,也许你可以帮助我。

我有一个用作工作队列的表。记录已插入并需要处理。处理完一条记录后,将其从队列中删除。有一些限制:

  • 在任何给定时间只有一个实体可以处理记录(“实体”是指:一个线程或连接到同一数据库的单独计算机)
  • 实体有些动态。它们可能会改变(实体的数量或特征)
  • 实体在 1 笔交易中处理记录
  • 处理必须并行进行(如果实体 1 选择批次 1,实体 2 必须能够并行处理批次 2,无需等待实体 1 完成处理)
  • 一旦实体选择了要处理的记录,则该记录所属的整个“批次”记录不得被其他实体选择。当我说“批处理”时,我的意思是该表(逻辑上)组织如下:
    • 第 1 行(批次 1)
    • 第 2 行(第 1 批)
    • 第 3 行(第 2 批)
    • 第 4 行(第 2 批)
    • 第 5 行(第 2 批)
    • .... 等等。

因此,假设 entity1 和 entity2 都想从表中选择一个处理切片。如果 entity1 选择 row1,则 entity2 可以选择除 batch1 之外的任何其他内容(除 row1 和 row2 之外的任何其他内容)。

让我们抽象出处理部分,因为实际处理是什么并不重要。我很想知道如何阻止实体相互冲突,只使用 mysql 数据库,同时保持处理的并行性。

从我的角度来看,我看到了两个非常普遍的方向:

  1. 使用某种状态字段,这表明特定实体已经选择了一批,并且必须将这个从未来的选择中排除。这个想法的缺点是如果选择批次的实体崩溃了,那么恢复其他实体的处理有点困难。
  2. 使用mysql的锁,缺点是难以保证并行处理,而且不是顺序的。例如,我可以为实体 1 选择...进行更新。但是 entity2 不能执行相同的 select... for update,因为这将等待第一个实体完成处理,然后再获取它需要的批次。

我有兴趣知道:

  • 哪个方向将导致最小的编码工作
  • 我在这里还缺少其他方向吗(请记住,除了通过数据库之外,实体无法相互通信)
  • 如果有这类问题的标准模式
  • 如果你能指出一篇讨论这类问题的文章。
  • 解决这个问题的最有效方法是什么。

所以我在这里是数据库必须在不同实体之间拆分一个表,以便处理,并且想知道最好的方法。我几乎不认为我是第一个处理这个问题的人,我想知道你的想法。另外,请注意,可以通过相当简单的标准(例如,batchId)分批拆分记录

亲切的问候,
安德烈。

4

2 回答 2

1

嗯,根据您的描述,我看不出数据库记录锁定将如何做您想要的。数据库锁说“如果这条记录不可用,请等待”,而不是“如果这条记录不可用,请选择另一个”。

问题:当一个批次被处理时,它是否“完成”?我的意思是,假设节点 A 开始处理第 1 批。然后节点 B 出现,但无论你怎么做,发现第 1 批正在工作,因此它从第 2 批开始。然后节点 A 完成。然后节点 C 出现。当前未处理第 1 批。节点 C 应该得到第 1 批吗?还是第 1 批已经完成,第 2 批正在工作,所以我们必须继续进行第 3 批?如果完成了批处理,那么任何数据库锁定显然都不起作用,因为它不记得过去曾访问过记录,只记得现在正在发生的事情。

也许其他人有一个更聪明的解决方案,但我认为你将不得不使用状态标志来做到这一点。我会说:

创建一个单独的 Batch 表,每批有一条记录。在指向批处理表的工作队列表中放置一个外键。这样我们就可以保持数据标准化。

在批处理表中,添加一个状态标志,其中包含工作中和非工作中的值;或未处理的、工作中的和已处理的。(取决于批次是否一劳永逸地“完成”。)还创建一个“处理实体”字段,该字段标识哪个实体正在处理该批次(如果它在工作中),否则为 null。(如果唯一的状态是工作中和非工作中,则该字段可以兼作工作中标志:null=not in-work,not null=in-work。)

当实体开始处理批次时,它会将处理实体字段设置为实体 ID。完成后,它将字段重置为空。当一个实体正在寻找要处理的批次时,它会选择“其中 processing_entity 为空”(当然还有其他任何条件)。

然后,为了确保实体没有崩溃而离开标志集,每次实体启动时,它都会检查是否有 Batch 记录表明它正在处理它。如果是这样,它一定已经崩溃了,所以它会重置标志并进行任何清理。

这仅在实体集是固定的情况下才有效,例如,如果实体是服务器,或者它是来自线程池的线程。如果它们是可以任意来来去去的线程,它就行不通,因为你不能指望线程重新启动来告诉它没有完成工作。

我使用的另一种方法是在批处理记录中放置一个时间戳,说明我们何时开始处理。然后我有另一个进程不时运行,检查时间戳,并与一些合理的最大时间进行比较。例如,如果我们知道该进程应该花费几秒钟,并且我们看到一个时间戳是一小时前的,那么该进程要么死亡,要么挂起,我们应该适当地进行清理。这确实有一个问题,它要求您能够说出最长时间是多少。

于 2012-05-07T21:25:31.653 回答
1

我过去对此的解决方案是使用“唯一编号”更新表中的“batch_id”列,然后使用该唯一批次编号从表中进行选择。

因此,在您的情况下,实体 1 出现:“UPDATE tbl SET batch_id='123456789' WHERE batch_id='0' LIMIT 1”然后“SELECT * FROM tbl WHERE batch_id='123456789'”

实体 2 出现并以其唯一的编号“15791579”重复该过程(因此它无法更新批次 1)并选择批次 2 等。

于 2013-03-15T18:48:47.577 回答