Amazon web services Amazon SimpleDB的困境:实现计数器属性

Amazon web services Amazon SimpleDB的困境:实现计数器属性,amazon-web-services,amazon-simpledb,Amazon Web Services,Amazon Simpledb,长话短说,我正在重写一个系统的一部分,并且正在寻找一种在AWS SimpleDB中存储一些命中计数器的方法 对于那些不熟悉SimpleDB的人来说,存储计数器的(主要)问题是云传播延迟通常超过一秒钟。我们的应用程序目前每秒获得约1500次点击。并非所有这些点击都会映射到同一个键,但大概的数字可能是每秒更新一个键5-10次。这意味着,如果我们使用传统的更新机制(读取、增量、存储),我们最终会无意中丢失大量的点击 一种可能的解决方案是将计数器保留在memcache中,并使用cron任务推送数据。最大

长话短说,我正在重写一个系统的一部分,并且正在寻找一种在AWS SimpleDB中存储一些命中计数器的方法

对于那些不熟悉SimpleDB的人来说,存储计数器的(主要)问题是云传播延迟通常超过一秒钟。我们的应用程序目前每秒获得约1500次点击。并非所有这些点击都会映射到同一个键,但大概的数字可能是每秒更新一个键5-10次。这意味着,如果我们使用传统的更新机制(读取、增量、存储),我们最终会无意中丢失大量的点击

一种可能的解决方案是将计数器保留在memcache中,并使用cron任务推送数据。最大的问题是这不是一种“正确”的方式。Memcache不应该真正用于持久性存储。。。毕竟,它是一个缓存层。此外,在推送时,我们会遇到问题,确保删除了正确的元素,并希望在删除它们时不会出现争用(这很可能)

另一个可能的解决方案是保留一个本地SQL数据库并在那里写入计数器,每这么多请求更新一次带外SimpleDB,或者运行cron任务来推送数据。这就解决了同步问题,因为我们可以包含时间戳来轻松设置SimpleDB推送的边界。当然,还有其他一些问题,尽管这可能需要相当数量的黑客攻击,但这似乎不是最优雅的解决方案


有没有人在他们的经历中遇到过类似的问题,或者有什么新颖的方法?任何建议或想法都将受到欢迎,即使它们没有被完全排除。我考虑这个问题已经有一段时间了,可以使用一些新的观点。

现有的SimpleDB API自然不适合作为分布式计数器。但这肯定是可以做到的

严格在SimpleDB中工作有两种方法可以让它工作。一种简单的方法,需要像cron作业这样的东西来清理。或者是一种更为复杂的技术,可以在进行时进行清洁

捷径 简单的方法是为每个“命中”制作不同的项目。使用一个作为键的属性。快速轻松地使用计数泵送域。当您需要获取计数时(可能很少),您必须发出查询

SELECT count(*) FROM domain WHERE key='myKey'
当然,这将导致您的域无限增长,并且随着时间的推移,执行查询的时间将越来越长。解决方案是一个汇总记录,其中汇总到目前为止为每个键收集的所有计数。它只是一个项,具有键{summary='myKey'}的属性和粒度低至毫秒的“上次更新”时间戳。这还需要将“timestamp”属性添加到“hit”项目中。摘要记录不需要位于同一域中。事实上,根据您的设置,它们最好保存在单独的域中。无论哪种方式,您都可以使用键作为itemName,并使用GetAttributes,而不是执行选择

现在获取计数需要两个步骤。您必须提取摘要记录,并查询严格大于摘要记录中“上次更新”时间的“时间戳”,然后将这两个计数相加

SELECT count(*) FROM domain WHERE key='myKey' AND timestamp > '...'
您还需要一种定期更新摘要记录的方法。您可以按计划(每小时)执行此操作,也可以根据某些其他条件动态执行此操作(例如,每当查询返回多个页面时,在常规处理期间执行此操作)。只要确保当你更新你的摘要记录时,你所依据的时间已经足够远了,你已经超过了最终的一致性窗口。1分钟是非常安全的

此解决方案适用于并发更新,因为即使同时写入多个摘要记录,它们都是正确的,并且无论哪一个获胜都是正确的,因为计数和“上次更新”属性将彼此一致

这也适用于多个域,即使您将摘要记录与命中记录一起保存,您也可以同时从所有域中提取摘要记录,然后并行地向所有域发出查询。这样做的原因是,如果一个密钥需要比一个域更高的吞吐量

这与缓存配合得很好。如果缓存失败,您将拥有权威备份

有人希望返回并编辑/删除/添加具有旧“Timestamp”值的记录的时间将会到来。此时您必须更新(该域的)摘要记录,否则在重新计算该摘要之前,您的计数将被关闭

这将为您提供与一致性窗口中当前可查看的数据同步的计数。这不会给你一个精确到毫秒的计数

艰难的道路 另一种方法是执行正常的读增量存储机制,但也要编写一个复合值,其中包括版本号和值。其中,您使用的版本号比正在更新的值的版本号大1

get(key)返回属性值=“Ver015 Count089”

在这里,您检索存储为版本15的计数89。执行更新时,您会写入如下值:

      11 --- 12
     /
10 --- 11
     \
       11
put(key,value=“Ver016 Count090”)

前一个值是而不是被删除,您最终会得到更新的审计跟踪,这让人想起兰波特时钟

这需要你做一些额外的事情

  • 在执行GET时识别和解决冲突的能力
  • 一个简单的版本号是行不通的,你可能希望包含一个分辨率至少为毫秒的时间戳,还可能包含一个进程ID
  • 实际上,您希望您的值包括当前版本号和版本号
    begin
      attributes = SimpleDB.GetAttributes
      initial_version = attributes[:version]
      attributes[:counter1] += 3
      attributes[:counter2] += 7
      attributes[:version] += 1
      SimpleDB.PutAttributes(attributes, :expected => {:version => initial_version})
    rescue ConditionalCheckFailed
      retry
    end