天蓝色表存储中的原子操作

时间:2012-08-07 19:30:54

标签: azure azure-table-storage

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

4 个答案:

答案 0 :(得分:23)

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

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

  1. 进程A读取PageViews的值并接收100。
  2. 进程B读取PageViews的值并接收100。
  3. 进程A对PageViews进行条件更新,这意味着“将PageViews设置为101,只要它当前为100”。这成功了。
  4. 进程B执行相同的操作并失败,因为前置条件(PageViews == 100)为false。
  5. 收到错误时,显而易见的事情是重复此过程。 (读取当前值,现在为101,并更新为102.)这将始终(最终)导致您的计数器具有正确的值。

    还有其他可能性,我们完成了关于如何实现真正可扩展的计数器的整个云封面集:http://channel9.msdn.com/Shows/Cloud+Cover/Cloud-Cover-Episode-43-Scalable-Counters-with-Windows-Azure

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

    修改

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

答案 1 :(得分:9)

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

第1步 - 录制网页浏览量

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

  • PartitionKey = PageName
  • 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. 某处更新计数
  5. 删除计算时考虑的记录
  6. 这里的问题是很难将其变成幂等的过程。如果您的实例在计数和删除之间崩溃会发生什么?您的页面数量会增加,但由于这些项目未被删除,因此下次处理时它们会被添加到总计数中。

    这就是我建议如下的原因。在同一表(PageViews)中,您还将在同一分区中记录总页面视图。但数据会有所不同(这将是该分区中包含总计数的单个记录):

    • PartitionKey = PageName
    • RowKey = Guid.Empty(只是不要使用随机guid,这样我们就知道记录的页面视图与持有总计数的记录之间的区别)。
    • Count =当前页面查看次数

    这是完全可能的,因为表存储模式较少。为什么我们这样做?因为我们确实有交易,如果我们将自己限制在最多100个实体的同一个表+分区。我们能做些什么?

    1. 使用Take,我们从该表+分区获得100条记录。
    2. 我们得到的第一条记录是“柜台”记录。为什么?因为它的rowkey是Guid.Empty而排序是lexicographical
    3. 计算这些记录(-1因为第一条记录不是页面视图,它只是我们的反占位符)
    4. 更新计数器记录的Count属性
    5. 删除99(或更少)其他记录
    6. 使用批处理保存更改。
    7. 重复,直到只剩下1条记录(计数器记录)。
    8. 每隔X分钟,你的工人会看到blob上是否有租约,获得租约并重新启动流程。

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

答案 2 :(得分:1)

我提出了同样的问题。使用Azure python库,我正在使用eTagIf-Match而不是锁来开发一个简单的计数器增量。基本思路是重试增加计数器,直到更新在某个条件下成功运行,这是其他更新不会干扰此运行更新。如果更新请求很重,则应调用分片。

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

答案 3 :(得分:1)

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