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

Sta*_*tes 16 azure azure-table-storage

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

use*_*559 24

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

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

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

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

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

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

编辑

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


San*_*tia 10

你也可以重新考虑'计数'部分.为什么不把它变成两步过程?

第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. 删除计数时考虑的记录

这里的问题是很难将其转变为幂等过程.如果您的实例在计数和删除之间崩溃会发生什么?您将增加页数,但由于项目未被删除,因此下次处理时它们将被添加到总计数中.

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

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

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

  1. 使用Take,我们从该表+分区获得100条记录.
  2. 我们得到的第一条记录是"柜台"记录.为什么?因为它的rowkey是Guid.Empty,排序是词典
  3. 计算这些记录(-1因为第一条记录不是页面视图,它只是我们的反占位符)
  4. 更新计数器记录的Count属性
  5. 删除99(或更少)其他记录
  6. SaveChanges使用Batch.
  7. 重复,直到只剩下1条记录(计数器记录).

每隔X分钟,您的工作人员将会看到blob上是否有租约,获得租约并重新启动流程.

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