【发布时间】:2017-02-15 01:19:50
【问题描述】:
我希望在 azure 表存储中实现页面查看计数器。如果说两个用户同时访问该页面,并且PageViews的当前值=100,是否保证更新操作后PageViews=102?
【问题讨论】:
-
我不知道这个问题,但为什么不以每月 5 美元的价格添加一个 100 mb 的 SQL 数据库。 SQL 处理锁。
我希望在 azure 表存储中实现页面查看计数器。如果说两个用户同时访问该页面,并且PageViews的当前值=100,是否保证更新操作后PageViews=102?
【问题讨论】:
答案取决于您如何实现计数器。 :-)
表存储没有“增量”运算符,因此您需要读取当前值 (100) 并将其更新为新值 (101)。表存储采用乐观并发,因此如果您在使用 .NET 存储客户端库时自然而然地进行操作,当两个进程尝试同时执行此操作时,您可能会看到异常。这将是流程:
当您收到错误时,显而易见的做法是重复该过程。 (读取当前值,现在是 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 来完成的。)
【讨论】:
您还可以重新考虑“计数”部分。为什么不把它变成一个两步的过程呢?
第 1 步 - 记录页面浏览量
每次有人查看页面时,都会向表中添加一条记录(我们称之为 PageViews)。您将在这些商店之一中添加的信息如下:
查看几次后,您会看到如下内容:
第 2 步 - 计算页面浏览量
我们现在要做的是获取所有这些记录,对它们进行计数,在某处增加一个计数器并删除所有记录。假设您有多个工作人员正在运行。您的两个工人都会在 1 到 10 分钟之间随机运行一个循环。每次工人的时间过去,如果还没有租用,它将在 blob 上租用(这应该始终是同一个 blob,您可以使用 AutoRenewLease)。
第一个拿到锁的工人可以继续计数:
这里的问题是很难把它变成一个幂等过程。如果您的实例在计数和删除之间崩溃会发生什么?您的页数会增加,但由于这些项目没有被删除,它们会在您下次处理它们时被添加到总页数中。
这就是我建议以下内容的原因。在同一张表 (PageViews) 中,您还将记录同一分区中的总页面浏览量。但是数据会有点不同(这将是该分区中的单个记录,保存总计数):
这是完全可能的,因为表存储架构较少。我们为什么要这样做?因为如果我们将自己限制在同一个表 + 最多 100 个实体的分区中,我们确实有事务。我们可以用这个做什么?
每隔 X 分钟,您的工作人员将查看 blob 是否没有租约,获取租约并重新启动流程。
这个答案足够清楚还是我应该添加一些代码?
【讨论】:
我也有同样的问题。使用 Azure python 库,我正在开发一个使用 eTag 和 If-Match 而不是锁定的简单计数器增量。基本思想是重试增加计数器,直到更新在某个标准下成功运行,也就是没有其他更新干扰这个正在运行的更新。如果更新请求很重,应该调用分片。
https://github.com/flyakite/simple-scalable-datastore/blob/master/datastore/azuretable.py
【讨论】:
如果使用 Azure 网站,那么 Azure 队列和 WebJobs 是另一种选择。 在我的一种情况下,虽然我实际上将采用分片方法并让 WebJobs 定期更新聚合。具有 PartitionKey = User 和 RowKey = Page 的 UserPageViews 的 Azure 表存储表。不允许两个具有相同用户 ID 的用户同时使用。
【讨论】: