现有的 SimpleDB API 不能自然地成为分布式计数器。但它肯定是可以做到的。
在 SimpleDB 中严格工作有两种方法可以使其工作。一种简单的方法,需要诸如 cron 作业之类的东西来清理。或者是一种更复杂的技术,可以随时清洁。
简单的方法
简单的方法是为每个“命中”制作不同的项目。具有单个属性,这是关键。快速轻松地抽取具有计数的域。当您需要获取计数时(假设频率要低得多),您必须发出查询
SELECT count(*) FROM domain WHERE key='myKey'
当然,这将导致您的域无限增长,并且随着时间的推移执行查询将花费越来越长的时间。解决方案是汇总记录,您可以在其中汇总到目前为止为每个键收集的所有计数。它只是一个具有键 {summary='myKey'} 属性和“上次更新”时间戳的项目,粒度低至毫秒。这还要求您将“时间戳”属性添加到“命中”项目。摘要记录不需要在同一个域中。事实上,根据您的设置,最好将它们保存在单独的域中。无论哪种方式,您都可以将密钥用作 itemName 并使用 GetAttributes 而不是执行 SELECT。
现在获得计数是一个两步过程。您必须提取摘要记录并查询严格大于摘要记录中“上次更新”时间的“时间戳”,并将两个计数加在一起。
SELECT count(*) FROM domain WHERE key='myKey' AND timestamp > '...'
您还需要一种定期更新摘要记录的方法。您可以按计划(每小时)执行此操作,也可以根据其他一些条件动态执行此操作(例如,只要查询返回超过一页,就在常规处理期间执行此操作)。只需确保当您更新摘要记录时,您所基于的时间已经足够远,以至于您已经过了最终的一致性窗口。1 分钟是安全的。
此解决方案适用于并发更新,因为即使同时写入许多摘要记录,它们都是正确的,并且无论哪个获胜仍然是正确的,因为计数和“Last-Updated”属性将与每个一致其他。
即使您将摘要记录与命中记录一起保存,这也适用于多个域,您可以同时从所有域中提取摘要记录,然后并行向所有域发出查询。这样做的原因是如果您需要比从一个域获得的密钥更高的吞吐量。
这适用于缓存。如果您的缓存失败,您将拥有权威备份。
有人想要返回并编辑/删除/添加具有旧“时间戳”值的记录的时间将会到来。届时您将不得不更新您的摘要记录(针对该域),否则您的计数将被取消,直到您重新计算该摘要。
这将为您提供与一致性窗口中当前可查看的数据同步的计数。这不会为您提供精确到毫秒的计数。
艰难的道路
另一种方法是执行正常的读取 - 增量 - 存储机制,但也写入一个复合值,其中包括版本号以及您的值。您使用的版本号比您正在更新的值的版本号大 1。
get(key) 返回属性值="Ver015 Count089"
在这里,您检索存储为版本 15 的计数 89。当您进行更新时,您会写入如下值:
put(key, value="Ver016 Count090")
之前的值不会被删除,您最终会得到一个让人想起灯时钟的更新审计跟踪。
这需要你做一些额外的事情。
- 每次执行 GET 时识别和解决冲突的能力
- 一个简单的版本号是行不通的,您需要包含一个分辨率至少为毫秒的时间戳,还可能包含一个进程 ID。
- 实际上,您会希望您的值包含当前版本号和更新所基于的值的版本号,以便更轻松地解决冲突。
- 您不能在一个项目中保留无限的审计跟踪,因此您需要随时删除旧值。
你用这种技术得到的就像一棵不同的更新树。您将拥有一个值,然后突然之间会发生多个更新,并且您将拥有一堆基于相同旧值的更新,而这些更新彼此都不知道。
当我说在 GET 时解决冲突时,我的意思是,如果您读取一个项目并且该值如下所示:
11 --- 12
/
10 --- 11
\
11
您必须能够计算出实际值是 14。如果您为每个新值包含要更新的值的版本,您可以做到这一点。
这不应该是火箭科学
如果你想要的只是一个简单的计数器:这就是过度杀戮。制作一个简单的计数器不应该是火箭科学。这就是为什么 SimpleDB 可能不是制作简单计数器的最佳选择。
这不是唯一的方法,但如果您实施 SimpleDB 解决方案而不是实际拥有锁,则需要完成大多数这些事情。
不要误会,我其实很喜欢这种方法,正是因为没有锁,并且可以同时使用这个计数器的进程数限制在100左右。(因为项目中的属性数量有限制)通过一些更改,您可以超过 100 个。
笔记
但是,如果所有这些实现细节都对您隐藏,而您只需要调用 increment(key),那么它一点也不复杂。使用 SimpleDB,客户端库是使复杂事物变得简单的关键。但是目前没有公开的库可以实现这个功能(据我所知)。