【问题标题】:Atomic operations in azure table storageAzure 表存储中的原子操作
【发布时间】:2017-02-15 01:19:50
【问题描述】:

我希望在 azure 表存储中实现页面查看计数器。如果说两个用户同时访问该页面,并且PageViews的当前值=100,是否保证更新操作后PageViews=102?

【问题讨论】:

  • 我不知道这个问题,但为什么不以每月 5 美元的价格添加一个 100 mb 的 SQL 数据库。 SQL 处理锁。

标签: azure azure-table-storage


【解决方案1】:

答案取决于您如何实现计数器。 :-)

表存储没有“增量”运算符,因此您需要读取当前值 (100) 并将其更新为新值 (101)。表存储采用乐观并发,因此如果您在使用 .NET 存储客户端库时自然而然地进行操作,当两个进程尝试同时执行此操作时,您可能会看到异常。这将是流程:

  1. 进程 A 读取 PageViews 的值并收到 100。
  2. 进程 B 读取 PageViews 的值并收到 100。
  3. 进程 A 对 PageViews 进行条件更新,这意味着“只要当前为 100,就将 PageViews 设置为 101”。这成功了。
  4. 进程 B 执行相同的操作并失败,因为前提条件 (PageViews == 100) 为假。

当您收到错误时,显而易见的做法是重复该过程。 (读取当前值,现在是 101,然后更新为 102。)这将始终(最终)导致您的计数器具有正确的值。

还有其他可能性,我们制作了完整的 Cloud Cover 集来介绍如何实现真正可扩展的计数器:http://channel9.msdn.com/Shows/Cloud+Cover/Cloud-Cover-Episode-43-Scalable-Counters-with-Windows-Azure

如果不太可能发生碰撞,那么该视频中描述的内容可能有点矫枉过正。即,如果您的命中率为每秒一次,则正常的“读取、递增、写入”模式将是安全且高效的。另一方面,如果您每秒收到 1000 次点击,那么您会想要做一些更聪明的事情。

编辑

只是想澄清一下阅读本文以了解乐观并发的人...条件操作并不是真正“只要当前为 100 就将 PageViews 设置为 101”。这更像是“将 PageViews 设置为 101,只要它自我上次查看以来没有改变。” (这是通过使用 HTTP 请求中返回的 ETag 来完成的。)

【讨论】:

  • 我建议使用 AutoRenewLease.DoOnce(我们的好朋友 smarx,blog.smarx.com/posts/…):)
  • 尽管我很喜欢人们使用我的代码 :-),但我认为我不知道这对这里有什么帮助?
  • 糟糕,DoOnce 是错误的,但我会在名称 = PageView.PartitionKey + "_" + PageView.RowKey 的 blob 上使用 AutoRenewLease。一旦它锁定了 blob,它将获得该记录并增加计数。通过这种方式,您可以确保每个页面视图都被考虑在内。所有这些都使用瞬态故障处理来确保 - 在出现问题时 - 代码重试,直到它可以记录页面浏览量。
  • 但是为什么呢?您建议“获取锁(根据需要重复),读取值,写入值,释放锁”而不是“读取值,尝试写入值,重复(仅在发生冲突时)”。对于低容量,仅使用表存储会产生更少的事务。对于大容量,你真的应该做一些像shard the counter之类的事情。什么时候最好使用 blob 租约?
  • 就个人而言,我会执行读取值并尝试直到成功。我唯一担心的是,这不会显着增加响应时间吗?我想知道我是否可以使用任何异步技术来做到这一点..hmm。
【解决方案2】:

您还可以重新考虑“计数”部分。为什么不把它变成一个两步的过程呢?

第 1 步 - 记录页面浏览量

每次有人查看页面时,都会向表中添加一条记录(我们称之为 PageViews)。您将在这些商店之一中添加的信息如下:

  • PartitionKey = 页面名称
  • RowKey = 随机 GUID

查看几次后,您会看到如下内容:

  • MyPage.aspx - someGuid
  • MyPage.aspx - someGuid
  • SomePage.aspx - someGuid
  • MyPage.aspx - someGuid

第 2 步 - 计算页面浏览量

我们现在要做的是获取所有这些记录,对它们进行计数,在某处增加一个计数器并删除所有记录。假设您有多个工作人员正在运行。您的两个工人都会在 1 到 10 分钟之间随机运行一个循环。每次工人的时间过去,如果还没有租用,它将在 blob 上租用(这应该始终是同一个 blob,您可以使用 AutoRenewLease)。

第一个拿到锁的工人可以继续计数:

  1. 从 PageViewRecordings 表或缓存中获取所有记录
  2. 统计每页的所有页面浏览量
  3. 在某处更新计数
  4. 删除计数时考虑的记录

这里的问题是很难把它变成一个幂等过程。如果您的实例在计数和删除之间崩溃会发生什么?您的页数会增加,但由于这些项目没有被删除,它们会在您下次处理它们时被添加到总页数中。

这就是我建议以下内容的原因。在同一张表 (PageViews) 中,您还将记录同一分区中的总页面浏览量。但是数据会有点不同(这将是该分区中的单个记录,保存总计数):

  • PartitionKey = 页面名称
  • RowKey = Guid.Empty(不要使用随机的 guid,这样我们就可以知道记录的页面浏览量和保存总计数的记录之间的差异)。
  • Count = 当前页面浏览次数

这是完全可能的,因为表存储架构较少。我们为什么要这样做?因为如果我们将自己限制在同一个表 + 最多 100 个实体的分区中,我们确实有事务。我们可以用这个做什么?

  1. 使用 Take,我们从该表 + 分区中获取 100 条记录。
  2. 我们将获得的第一条记录是“计数器”记录。为什么?因为它的 rowkey 是 Guid.Empty 并且排序是字典式的
  3. 统计这些记录(-1,因为第一条记录不是页面浏览量,它只是我们的计数器占位符)
  4. 更新计数器记录的 Count 属性
  5. 删除其他 99 条(或更少)记录
  6. 使用批处理保存更改。
  7. 重复直到只剩下 1 条记录(计数器记录)。

每隔 X 分钟,您的工作人员将查看 blob 是否没有租约,获取租约并重新启动流程。

这个答案足够清楚还是我应该添加一些代码?

【讨论】:

  • 现在我明白你所说的关于 blob 租约的意思了。这是明智的,但我仍然更喜欢分片之类的东西(就像在 Cloud Cover 情节中一样)。
  • 我喜欢这个主意。不需要代码,我理解的很清楚。但你的意思是,“如果没有租约,那么工作人员会获得租约并重新启动流程”对吗?
【解决方案3】:

我也有同样的问题。使用 Azure python 库,我正在开发一个使用 eTagIf-Match 而不是锁定的简单计数器增量。基本思想是重试增加计数器,直到更新在某个标准下成功运行,也就是没有其他更新干扰这个正在运行的更新。如果更新请求很重,应该调用分片。

https://github.com/flyakite/simple-scalable-datastore/blob/master/datastore/azuretable.py

【讨论】:

    【解决方案4】:

    如果使用 Azure 网站,那么 Azure 队列和 WebJobs 是另一种选择。 在我的一种情况下,虽然我实际上将采用分片方法并让 WebJobs 定期更新聚合。具有 PartitionKey = User 和 RowKey = Page 的 UserPageViews 的 Azure 表存储表。不允许两个具有相同用户 ID 的用户同时使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-11
      • 2011-02-16
      • 1970-01-01
      • 2018-08-05
      • 2012-07-31
      • 1970-01-01
      相关资源
      最近更新 更多