我怎样才能确保我不会遇到这种竞争条件?

时间:2015-02-22 17:55:02

标签: c# sql-server race-condition azure-webjobssdk azure-storage-queues

我当前的流程以下列方式运行:

1。)用户在前端应用程序中输入URL以进行分析

2。)前端验证URL并在包含URL属性的表中创建URL记录

3。)前端创建/更新表中的一行,用于跟踪URL所处的处理阶段(每个URL都有自己的内部ID)

3.A)状态代码更新为“排队”状态

----表定义:

ID INT PRIMARY KEY,
StatusCode INT,
StatusDescription VARCHAR(MAX),
IsInitial BIT,
LastUpdated DATETIME

4.。)前端向Azure存储队列发送包含提交的URL内部ID的消息

将第一封邮件发送到队列后------------>

4.A)在UI中创建一个对象供用户点击(“刷新”数据)

4.B)用户在创建的对象上点击(很可能会发生这种情况)(如果经过验证,则立即点击)

4.C)另一条消息被发送到包含URL ID

的队列

< --------------------------------

5.。)运行azure webjob(后台任务)持续选择这些消息并开始处理

6。)webjob确定此URL是否可以处理

.....如果

,就可以开始处理了
     
  • 它是新的(LastUpdated字段为空)
  •  
  • 附加到该项目的状态代码表示错误
  •  
  • 自LastUpdated以来已过去15分钟

.....如果退出

     
  • 邮件中的ID无效
  •  
  • 附加的状态代码表示目前正在处理
  •  
  • 自LastUpdated以来不到15分钟

一旦确定被认为可以继续......

  • 如果是新的,则webjob会将LastUpdated更新为Datetime.Now
  • 在流程的每个步骤开始时,状态代码会更新以反映此
  • 在流程的最后,LastUpdated更新为当前时间

  • try catch围绕着这个过程

a。)如果进程出错,则更新状态代码以反映

b。)新消息被推入队列以进行重试

确定网址是否可以解析的功能:

    private bool IsReadyToParse(int [ID])
    {
        using (var db = EntityFactory.GetInstance())
        {
            var item = db.ProcessStatus.FirstOrDefault(x => x.ID == [ID]);

            if (item == null || item.StatusCode > 1)
            {
                return false;
            }

            if (item.StatusCode == (int)ProcessStatusEnum.Error || item.LastUpdated == null)
            {
                item.LastUpdated = DateTime.Now;
                db.Entry(item).State = EntityState.Modified;
                db.SaveChanges();
                return true;
            }

            return ((DateTime)item.LastUpdated).AddMinutes(15) < DateTime.Now;
        }
    }

队列消息通过此功能进入:

     // This function will get triggered/executed when a new message is written 
    // on an Azure Queue
    public static void ProcessQueueMessage([QueueTrigger("[queue]")] QueueItem item, TextWriter log)
    {
        Console.WriteLine("Item found! Starting services [Id: {0}]", item.ID);

        Agent agent = new Agent([ID], log);
        agent.StartProcessing();

        log.WriteLine([Item]);
    }

...现在的问题是,这个持续运行的webjob一次可以收到多条消息(我希望将其扩展到另外几个从同一队列中读取的webjobs)

我如何确定函数IsReadyToParse()实际上反映了当前的处理状态?

如果数据库只是要将状态代码更新为“进行中”,但是另一个线程只是读取状态代码并且还可以继续执行该过程怎么办?

2 个答案:

答案 0 :(得分:0)

这是我有限的开发人员测试的基本解决方案...随着我的进展将会更新。

...... 使用此存储过程而不是IsReadyToParse()

CREATE PROCEDURE dbo.usp_getIsReadyForProcess
@[ID] INT 
AS
BEGIN
 BEGIN TRY

  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
  BEGIN TRANSACTION

    DECLARE @lastUpdated DATETIME
    DECLARE @statusCode INT

    -- LOCK ROW UNTIL END OF TRANSACTION
    SET @lastUpdated = (SELECT LastUpdated FROM dbo.ProcessStatus WITH (ROWLOCK, HOLDLOCK) WHERE [ID] = @[ID])
    SET @statusCode = (SELECT StatusCode FROM dbo.ProcessStatus WHERE [ID] = @[ID])

    DECLARE @isReady BIT

    --If there is no row count
    IF @@ROWCOUNT = 0
    BEGIN
        SET @isReady = 0
    END

    -- If video is already in process
    ELSE IF @statusCode > 1
    BEGIN
        SET @isReady = 0
    END

    -- If this is the first time it is getting parsed
    ELSE IF @lastUpdated IS NULL
    BEGIN
        SET @isReady = 1

        --Update datetime field
        UPDATE dbo.ProcessStatus 
        SET LastUpdated = GETDATE()
        WHERE [ID] = @[ID]
    END

    -- If is isnt the initial parse and hasnt been 15 minutes yet
    ELSE IF GETDATE() < DATEADD(MINUTE, 15, @lastUpdated)
    BEGIN
        SET @isReady = 0
    END


    -- Anything else, and its a go 
    ELSE 
    BEGIN
        SET @isReady = 1
    END



    -- If were ready to start, update the status code
    IF @isReady = 1
    BEGIN
        UPDATE dbo.ProcessStatus 
        SET StatusCode = 2 
        WHERE [ID] = @[ID]
    END

 COMMIT TRANSACTION

 SELECT @isReady

 END TRY    
 BEGIN CATCH
    -- If there was any type of error
    ROLLBACK
    SELECT 0
 END CATCH 
END

答案 1 :(得分:0)

这是一种可能的方法,类似于WebJobs SDK在内部执行的操作,以防止更多的webjob函数同时处理相同的blob触发器。

当函数从队列中获取消息时,请创建一个与消息中的ID同名的blob。 blob的内容是处理的状态(Done或InProgress)。当一个函数想要处理带有该ID的消息时,它必须对该blob进行租约 - 这可以保证线程的安全性。然后:

  • 如果它不能获得至少,则其他人正在处理该消息=&gt;丢弃队列消息。
  • 如果它获得了租约但状态为“完成”,则某人已经处理了该消息=&gt;丢弃队列消息。
  • 如果它获得了租约且状态为“进行中”,则有人试图处理该消息但无法完成=&gt;再次使用该消息并处理。

如果处理邮件的时间超过60秒,您将需要一些额外的代码来续订blob租约,否则它将会过期而其他人可以将其提取。