18

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

4

4 回答 4

28

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

表存储没有“增量”运算符,因此您需要读取当前值 (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-与-Windows-Azure

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

编辑

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

于 2012-08-07T20:09:38.940 回答
11

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

第 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 上是否没有租约,获取租约并重新启动流程。

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

于 2012-08-07T22:47:12.973 回答
1

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

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

于 2014-02-28T03:43:53.943 回答
1

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

于 2014-08-07T09:06:10.847 回答