我使用SQL Server数据库表作为工作队列。
我有一位作家和多位读者。
编写器为队列表生成新记录(INSERT)。
每个阅读器查找要使用的特定状态(SELECT)的记录,获取一批记录,将每个记录标记为已拥有(UPDATE),处理它们,然后将所有已处理的记录标记为完成或失败(UPDATE)。记录的工作流程很简单:状态从“A”表示“已添加”,“B”表示“正在处理”,“C”表示“完成”或“F”表示“失败”。每个记录的所有者从状态“A”中的未设置变为唯一标识符,用于标识读取器的剩余步骤。唯一标识符(十进制)加上状态(nchar(1))加上标识作者较大批次的附加批处理ID(int)是处理中的三个关键字段。此外,该表还有一个LINQ用于并发检查的时间戳字段。
由于两位读者选择要处理的相同记录,我需要防止锁定延迟和更新失败。作为处理工作的一部分,进行Web服务调用可能需要不确定的时间来完成。我们通过交易向Web服务供应商付款,因此我不想进行调用,然后找出处理相同记录的另一个进程。这样的错误将花费我们数千美元。
我读过这篇文章:
Processing Data Queues in SQL Server with READPAST and UPDLOCK
看起来很有希望,但我决定使用LINQ to SQL。我读了这篇文章:
后者说没有办法告诉LINQ使用UPDLOCK而不是粗略地包装SQL过程。这是因为LINQ使用乐观并发,并且这些功能假设是悲观锁定。
那么如何使用LINQ支持的功能解决这个问题呢?我的应用程序是多线程的,但我可以有多个实例针对同一个队列表运行,所以我不能让一个调度程序将记录交给每个消费者线程。
我正在使用.NET 4.0。我的应用程序是一个普通的Windows控制台应用程序该解决方案应与SQL Server 2005和SQL Server 2008 R2一起使用。我不想使用消息传递系统。
更新:对SO的进一步研究。找到这篇文章:
答案建议有一个调度员线程,我已经排除了。
更新2:找到关于SO的另一篇文章:
Atomically Mark and return a group of rows in database
这看起来很有希望。我需要执行直接SQL,但是一旦我记录了我的记录并获得了我的ID,其余的就可以在L2S中进行。
答案 0 :(得分:0)
也许你可以对你的读者中的出列过程采取更悲观的态度。
让读者收到下一个排队的项目。让读者尝试将该项目出列。读者可以为出队呼叫中的项目分配唯一值,例如guid。
然后,读者可以尝试获取已排队的排队项目。当它收到该项目时,它可以比较它已分配的项目的guid。如果它们是相同的,那么读者就可以进行处理。
假设您的读者可以一次出列一个项目,这应该是可行的。我不确定每个读者出列多个项目时该怎么办。
答案 1 :(得分:0)
服务这是我的答案。我将存储过程与我的DataContext相关联以保留地址。它可以使用悲观并发在单个操作中选择,标记和返回记录。
CREATE PROCEDURE dbo.kccsp_ReserveAddresses
@BATCH_SIZE int,
@BATCH_ID int,
@PROCESS_TOKEN numeric(18,0)
AS
-- Reserve a group of addresses in the queue so that a consumer may process them,
-- but all other consumers will leave them alone.
-- The query hints are essential to prevent lock contention,
-- or concurrency errors from multiple processors handling the same address.
update TOP (@BATCH_SIZE)
KCC_GeoCodingAddressQueue WITH (ROWLOCK, READPAST, UPDLOCK)
set
[Process Status] = 'B',
[Process Token] = @PROCESS_TOKEN
output INSERTED.*
where [Process Status] = 'A' and [Process Token] = 0
我是通过将过程拖到我的dbml设计器的结果表中来完成的。这会导致结果集与我的实体类相同,并在标记它们的同时返回我指定的所有记录。 SQL提示处理并发和锁争用。
当我需要在C#中调用它时,我这样做:
int? batchSize = 50;
int? batchIdToReserve = Batch.GeoBatchID;
decimal? processorId = someId;
var addresses = Context.kccsp_ReserveAddresses(batchSize, batchIdToReserve, processorId);