跨多个线程处理数据库队列 - 设计建议

时间:2010-05-27 15:43:26

标签: sql-server multithreading queue

我有一个SQL Server表,其中包含我的程序需要“跟进”的订单(调用web服务以查看是否已完成某些操作)。我的应用程序是多线程的,可以在多个服务器上运行实例。目前,每隔一段时间(在一个线程计时器上),该进程就会从“未经证实的”命令列表中随机选择100行(ORDER BY NEWID()),然后检查它们,标记出任何成功返回的行。

问题在于线程之间以及不同进程之间存在很多重叠,并且它们无法保证很快就会检查新订单。此外,一些订单永远不会被“确认”并且已经死亡,这意味着它们会妨碍需要确认的订单,如果我一遍又一遍地选择它们,会减慢流程。

我更喜欢的是系统地检查所有未完成的订单。我可以想到两个简单的方法:

  1. 应用程序一次提取一个订单进行检查,将其检查的最后一个订单作为参数传递,然后SQL Server回退下一个未经确认的订单。更多数据库调用,但这确保在合理的时间范围内检查每个订单。但是,不同的服务器可能会不必要地连续重新检查相同的订单。
  2. SQL Server会跟踪它要求进程检查的最后一个顺序,可能在表中,并为每个请求提供一个唯一的顺序,递增其计数器。这涉及将最后一个订单存储在SQL中的某个地方,这是我想避免的,但它也确保线程不会不必要地同时检查相同的订单
  3. 我还缺少其他任何想法吗?这甚至有意义吗?如果我需要澄清,请告诉我。


    结果:

    我最终做的是在我的表中添加一个LastCheckedForConfirmation列,其中包含已完成的订单,我添加了一个存储过程,使用GETDATE()更新单个未确认的行并启动订单号,以便我的流程可以检查它。它尽可能多地旋转(假设进程愿意运行的线程数),并使用存储过程为每个线程获取一个新的OrderNumber。

    要处理“不要尝试行太多次或太旧时”的问题,我这样做了:如果“自上次尝试以来的时间”> SP将只返回一行。 “创造和最后一次尝试之间的时间”,所以每次再次尝试需要两倍的时间 - 首先等待5秒,然后是10,然后是20,40,80,120,然后再尝试15次(6次)小时),它放弃了该订单,SP将永远不会再返回。

    感谢大家的帮助 - 我知道我这样做的方式不太理想,我很欣赏你指出正确的方向。

3 个答案:

答案 0 :(得分:7)

我建议阅读并内化Using tables as Queues

如果您将数据用作队列,必须正确组织数据以进行排队操作。我链接的文章详细介绍了如何执行此操作,您拥有的是Pending Queue的变体。

你必须绝对摆脱的一件事是随机性。如果在查询中有一件事难以重现,那就是随机性。 ORDER BY NEWID()扫描每一行,生成一个guid,然后排序,然后返回前100名。在任何情况下,您都不能让每个工作线程每次扫描整个表,随着未处理条目数量的增加,您将终止服务器。

而是使用待处理日期。通过处理日期列(当项目需要重试时)组织(聚集)队列,并使用我在链接文章中显示的技术出列队列。如果要重试,则出列推迟项目而不是删除它,即。 WITH (...) UPDATE SET due_date = dateadd(day, 1, getutcdate()) ...

答案 1 :(得分:2)

显而易见的方法是在订单中添加列LastCheckDt。在每个线程中,检索已经过去最长时间而不进行检查的订单。在检索订单的过程中,更新LastCheckDt字段。

我不会一次检索100个订单,在您的线程到达之前,数据库中存在第50个订单更改的风险。获得一个订单,完成后,获取下一个订单。

此外,我最初开发的过程没有多线程。检查未结订单通常足够快,可以顺序完成。

答案 2 :(得分:0)

您可能想要考虑的一个策略是这样的表格;

JobID bigint PK not null,WorkerID int / nvarchar(max)null

其中worker是正在处理它的服务器的id / name,如果没有人接收到该作业,则为null。当服务器选择一个作业时,它会将自己的id / name放入该列,以指示其他人不要接受该作业。

一个问题是,工作的服务器可能崩溃,使得工作永远不会完成。您可以添加一个代表超时的日期列,该值在工作人员将工作提取到现在时设置+您认为合适的时间范围。

编辑:忘记提及,您需要在完成时删除作业,或者有一个状态字段来表示完成。附加字段可以指示作业的参数,以使您的作业表通用:即。不要只为您的订单制定解决方案,而是要建立一个可以处理将来需要的任何工作的工作经理。