现有的 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,客户端库是使复杂事物变得简单的关键。但目前没有公开可用的库实现此功能(据我所知)。