更新Azure表存储记录时如何避免竞争状况

时间:2019-06-08 17:38:19

标签: azure azure-functions azureservicebus azure-table-storage race-condition

利用Azure表存储的Azure功能

我有一个从Azure Service Bus主题订阅触发的Azure函数,我们称其为“处理文件信息”函数。

订阅上的消息包含要处理的文件信息。与此类似:

{
  "uniqueFileId": "adjsdakajksajkskjdasd",
  "fileName":"mydocument.docx",
  "sourceSystemRef":"System1",
  "sizeBytes": 1024,
  ... and other data
}

该函数执行以下两个操作-

  1. 检查单个文件存储表中是否存在该文件。如果存在,请更新该文件。如果是新文件,请将文件添加到存储表中(按每个系统 | 每个fileId 进行存储)。

  2. 捕获有关文件大小字节的度量,并存储在第二个存储表中,该度量称为度量(不断增加字节,按每年/每月存储在系统 | 中< / strong>)。

下图简要说明了我的方法:

enter image description here

individualFileInfo 表与 fileMetric 之间的区别在于,单个表每个文件有一条记录,而度量标准表则每月存储一条记录,并且该记录保持不变更新(增加)以收集通过该函数传递的总字节。

fileMetrics表中的数据存储如下:

enter image description here

问题...

Azure功能在扩展方面非常出色,在我的设置中,我一次最多可以运行6个这些功能。假定要处理的每个文件消息都是唯一的-在没有竞争条件的情况下,更新 individualFileInfo 表中的记录(或插入)可以很好地工作。

但是,事实证明更新 fileMetric 表是有问题的,因为所有6个函数都立即触发,它们都打算一次更新指标表(不断增加新文件计数器或增加现有文件计数器)。

我已经尝试过使用etag进行乐观更新,以及从存储更新中返回412响应时进行一点递归重试(下面的代码示例)。但是我似乎无法避免这种竞争状况。有没有人对如何解决此约束或遇到类似问题提出任何建议?

在存储 fileMetric 更新的功能中执行的示例代码:

internal static async Task UpdateMetricEntry(IAzureTableStorageService auditTableService, 
    string sourceSystemReference, long addNewBytes, long addIncrementBytes, int retryDepth = 0)
{
    const int maxRetryDepth = 3; // only recurively attempt max 3 times
    var todayYearMonth = DateTime.Now.ToString("yyyyMM");
    try
    {
        // Attempt to get existing record from table storage.
        var result = await auditTableService.GetRecord<VolumeMetric>("VolumeMetrics", sourceSystemReference, todayYearMonth);

        // If the volume metrics table existing in storage - add or edit the records as required.
        if (result.TableExists)
        {
            VolumeMetric volumeMetric = result.RecordExists ?
                // Existing metric record.
                (VolumeMetric)result.Record.Clone()
                    :
                // Brand new metrics record.
                new VolumeMetric
                {
                    PartitionKey = sourceSystemReference,
                    RowKey = todayYearMonth,
                    SourceSystemReference = sourceSystemReference,
                    BillingMonth = DateTime.Now.Month,
                    BillingYear = DateTime.Now.Year,
                    ETag = "*"
                };

            volumeMetric.NewVolumeBytes += addNewBytes;
            volumeMetric.IncrementalVolumeBytes += addIncrementBytes;

            await auditTableService.InsertOrReplace("VolumeMetrics", volumeMetric);
        }
    }
    catch (StorageException ex)
    {
        if (ex.RequestInformation.HttpStatusCode == 412)
        {
            // Retry to update the volume metrics.
            if (retryDepth < maxRetryDepth)
                await UpdateMetricEntry(auditTableService, sourceSystemReference, addNewBytes, addIncrementBytes, retryDepth++);
        }
        else
            throw;
    }
}

Etag跟踪冲突,如果此代码获得412 Http响应,它将重试,最多3次(尝试缓解此问题)。我的问题是我不能保证在函数的所有实例之间都更新表存储。

感谢任何提前提示!

1 个答案:

答案 0 :(得分:1)

您可以将工作的第二部分放入第二个队列和功能,甚至可以触发文件更新。

由于其他操作听起来可能要花费大部分时间,因此它也可以从第二步中消除一些热量。

然后您可以仅关注该功能来解决所有剩余的比赛条件。您可以使用会话来有效地限制并发。在您的情况下,系统ID可能是会话密钥。如果使用该选项,则一次只有一个Azure Function处理来自一个系统的数据,从而有效地解决了您的竞争状况。

https://dev.to/azure/ordered-queue-processing-in-azure-functions-4h6c

编辑:如果您不能使用会话来逻辑上锁定资源,则可以通过blob存储使用锁定:

https://www.azurefromthetrenches.com/acquiring-locks-on-table-storage/