![]() |
1
20
现有的simpledb api本身并不适合作为分布式计数器。但这当然可以做到。 严格在simpledb中工作有两种方法可以使其工作。一种简单的方法,需要像cron作业这样的东西来清理。或者一种更复杂的清洁技术。 简单的方法简单的方法是为每一个“点击”制作不同的项目。只有一个属性,这是键。使用计数快速轻松地抽取域。当您需要获取计数(更不常见地假定)时,必须发出一个查询
当然,这将导致您的域无限增长,并且随着时间的推移,查询将需要越来越长的时间来执行。解决方案是一个摘要记录,您可以在其中汇总到目前为止为每个键收集的所有计数。它只是一个具有键summary='mykey'属性的项,以及一个粒度小于毫秒的“最后更新”时间戳。这还要求您将“timestamp”属性添加到“hit”项中。摘要记录不需要在同一个域中。实际上,根据您的设置,它们最好保存在单独的域中。无论采用哪种方法,都可以使用键作为项名称,并使用getattributes而不是执行select。 现在获取计数是一个两步的过程。您必须提取摘要记录并严格查询大于摘要记录中“上次更新”时间的“时间戳”,然后将这两个计数相加。
您还需要一种定期更新摘要记录的方法。您可以按计划(每小时)或基于某些其他条件动态地执行此操作(例如,当查询返回多个页面时,在常规处理过程中执行此操作)。只要确保在更新摘要记录时,将其建立在一个足够远的时间基础上,这样您就可以通过最终的一致性窗口。一分钟比安全多了。 此解决方案在面对并发更新时有效,因为即使同时写入了许多摘要记录,它们都是正确的,无论哪一个成功都将是正确的,因为计数和“上次更新”属性将彼此一致。 这也适用于多个域,即使将摘要记录与命中记录一起保存,也可以同时从所有域中提取摘要记录,然后并行向所有域发出查询。这样做的原因是,如果您需要比从一个域获得的吞吐量更高的密钥。 这对缓存很好。如果缓存失败,您将拥有权威备份。 当有人想要返回并编辑/删除/添加一个旧的“时间戳”值的记录时,时间就会到来。此时您必须更新您的摘要记录(针对该域),否则您的计数将关闭,直到您重新计算该摘要。 这将为您提供与一致性窗口中当前可查看的数据同步的计数。这不会给你精确到毫秒的计数。 艰难的道路另一种方法是执行正常的读取-增量-存储机制,但也编写一个包含版本号和值的复合值。如果使用的版本号大于正在更新的值的版本号,则为1。 get(key)返回属性value=“ver015 count089” 在这里,您将检索存储为版本15的89个计数。当您进行更新时,会编写如下值: 输入(key,value=“ver016 count090”) 上一个值是 不 删除后,您将得到一个类似于lamport时钟的更新审计跟踪。 这需要你做一些额外的事情。
使用这种技术所得到的结果就像是一棵不同的更新树。您将拥有一个值,然后突然发生多个更新,您将拥有一系列基于相同旧值的更新,这些旧值彼此都不知道。 当我说“在获取时解决冲突”时,我的意思是,如果您阅读了一个项目,并且该值如下所示:
您必须能够计算出实际值是14。如果为每个新值都包含要更新的值的版本,则可以执行此操作。 这不应该是火箭科学如果您只需要一个简单的计数器: 这太过分了 . 制造一个简单的计数器不应该是火箭科学。这就是为什么simpledb可能不是制作简单计数器的最佳选择。 这不是唯一的方法,但是如果您实现一个simpledb解决方案而不是实际拥有一个锁,那么大多数这些事情都需要完成。 别误会我,我确实喜欢这个方法,因为没有锁,并且可以同时使用这个计数器的进程数的限制在100左右。(由于一个项目中属性的数量有限),您可以通过一些更改超过100个。 注释但是,如果所有这些实现细节对您都是隐藏的,并且您只需要调用increment(key),那么它就一点也不复杂了。使用simpledb,客户机库是使复杂事物简单化的关键。但目前没有公开的库来实现这个功能(据我所知)。 |
![]() |
2
15
对于任何重新讨论这个问题的人,亚马逊只是增加了对 Conditional Puts, 这使得计数器的实现更加容易。
现在,要实现计数器,只需调用getattributes,增加计数,然后调用puttributes,并正确设置期望值。如果亚马逊回应错误
注意,每个puttributes调用只能有一个预期值。因此,如果您希望在一行中有多个计数器,那么可以使用version属性。 伪代码:
|
![]() |
3
2
我看你已经接受了一个答案,但这可能算是一种新颖的方法。 如果您正在构建一个Web应用程序,那么您可以使用谷歌的分析产品来跟踪页面印象(如果页面到域的项目映射适合),然后使用分析API定期将数据推送到项目本身。 我没有仔细考虑过,所以可能会有漏洞。鉴于您在该领域的经验,我对您对这种方法的反馈非常感兴趣。 谢谢 斯科特 |
![]() |
4
2
对于任何对我最终如何处理这件事感兴趣的人…(略微Java专用) 我最终在每个servlet实例上使用了ehcache。我使用UUID作为一个键,并将Java原子整数作为值。线程周期性地遍历缓存,并将行推送到simpledb temp stats域,并将带有键的行写入无效域(如果该键已经存在,则会自动失败)。线程还使用以前的值递减计数器,确保在更新时不会错过任何命中。单独的线程ping simpledb无效域,并在临时域中汇总统计信息(每个键有多行,因为我们使用的是EC2实例),将其推送到实际的统计信息域。 我做了一点负载测试,而且它的规模似乎很好。在本地,我能够在负载测试仪崩溃之前处理大约500次点击/秒(而不是servlets-hah),所以如果我认为在EC2上运行任何东西只会提高性能。 |
![]() |
5
1
费曼斯达德的回答: 如果您想要存储大量的事件,我建议您使用分布式提交日志系统,例如 kafka 或 aws kinesis . 它们允许消费便宜而简单的事件流(Kinesis的定价是每月25美元,每秒钟1千个事件)_瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮瘮T 只需使用nginx日志就可以记录事件,并使用 fluentd . 这是一个非常便宜,性能和简单的解决方案。 |
![]() |
6
0
也有相似的需求/挑战。 我研究过使用谷歌分析和count.ly。后者似乎太贵,不值得(另外,它们对会话的定义有些混乱)。我本来很想用的,但我花了两天时间使用他们的库和一些第三方的库(gadotnet和另一个来自codeproject)。不幸的是,我只能在GA Realtime部分看到计数器的发布,而在正常的仪表板中却看不到计数器的发布,即使API报告成功。我们可能做错了什么,但我们超出了GA的时间预算。 我们已经有了一个现有的simpledb计数器,它使用前面的注释中提到的条件更新进行更新。这很好地工作,但当存在争用和一致性时,计数会丢失(例如,与备份系统相比,我们最新的计数器在3个月内丢失了数百万计数)。 我们实现了一个新的解决方案,它与这个问题的答案有些相似,只是简单得多。 我们只是把柜台分开。当您创建计数器时,您指定碎片的数量,这是您期望的模拟更新数量的函数。这将创建许多子计数器,每个计数器都以它作为属性开始碎片计数: 计数器(带5shards)创建: shard0 numshards=5(仅供参考) shard1 count=0,numshards=5,timestamp=0 shard2 count=0,numshards=5,timestamp=0 shard3 count=0,numshards=5,timestamp=0 shard4 count=0,numshards=5,timestamp=0 shard5 count=0,numshards=5,timestamp=0 Sharded写道 知道碎片数后,随机挑选一块碎片,并有条件地写下来。如果由于争用而失败,请选择另一个碎片并重试。 如果你不知道碎片的数量,从存在的根碎片中得到它,不管有多少碎片。因为它支持每个计数器进行多次写入,所以它可以将争用问题减少到您需要的程度。 锐利阅读 如果你知道碎片的数量,读下每一块碎片并求和。 如果你不知道碎片的数量,从根碎片中得到它,然后读取全部和。 由于更新速度缓慢,您仍然可能会错过计数读数,但应该稍后再读取。这足以满足我们的需要,但是如果您希望对其进行更多的控制,您可以确保-在读取时-最后一个时间戳如您所期望的那样,然后重试。 |