我打算将cassandra用作我的应用程序的nosql数据存储。我有一个使用案例是更新用户的“余额”。假设每个用户的余额存储为密钥UID_balance。现在,如果我的应用程序想要更新多个用户的余额,我将如何处理原子性?
我想,在某些时候,应用程序基本上会执行以下操作:
1. for each user u
2. current_balance = read_users_balance(u);
3. new_balance = current_balance + delta_for_user(u);
4. write_users_balance(u, new_balance);
5. end
现在,这里有几个问题:
RDBMS'解决了这些问题,因为它们提供ACID属性,而Cassandra则没有。我最近看到Cassandra(2012年10月)已经开始提供Atomic Batches。我不确定这是否是解决这个问题的正确方法。
这是我和朋友一起集思广益的事情。我们实际上并不更新用户的余额,而是创建一条记录,将更新增量附加到不同的记录。例如:
UID1_balance = {100}
UID1_deltas = {10,20,-40}
为了获得当前余额,我们只需将增量应用于余额。我们可以有一个离线流程,将增量应用于用户的余额并修剪增量列表。
这个解决方案起作用并降低了腐败状态的可能性,但我认为这是一种矫枉过正。有没有更好的方法来解决这个问题?
答案 0 :(得分:6)
我建议阅读论文“建立在流沙上”这会让你考虑到帐户,他们甚至会参考像这样的银行帐户示例。注意:Chase和well fargo不会在交易中转账,因此他在那篇文章中解释了我们如何在微观层面上做同样的事情,就像宏观层面一样;)。
这也有助于PlayOrm for cassandra的编写,因为PlayOrm维基上也有一个模式页面。
答案 1 :(得分:2)
由于没有锁定,因此无法在Cassandra中使用您的初始“读取修改写入”方法。 Cassandra计数器部分解决了这个问题,但在两个地方达不到要求:
这意味着将增量存储为单独列的可能解决方案是获得所需保证的唯一方法,与Cassandra 1.2中的原子批次相结合(请参阅http://www.datastax.com/dev/blog/atomic-batches-in-cassandra-1-2)。您的解决方案就像实现计数器一样,每个计数器都在一行中,每个delta都是一列。要阅读,请总结一行中列的所有值。
正如你所说,这里的问题是处理垃圾,因为这些增量列表会随着时间的推移而增长。如果没有太多更新可以,但如果经常更新余额,那么阅读速度会太慢。
通过读取增量然后以原子方式删除它们并为整个值添加一个增量,可以使“垃圾收集”的离线过程安全。使用原子批次和单线程进程可以确保安全。
答案 2 :(得分:2)
正如理查德指出的那样,目前最好的方法是使用原子批次来更新许多增量。如果出现问题,只需重播批次。
另一种可能的解决方案是使用ZooKeeper作为协调和分布式锁定服务:http://ria101.wordpress.com/tag/zookeeper/
另一种可能的解决方案是使用counters,因此您不需要这样做
your current_balance = read_users_balance(u);
new_balance = current_balance + delta_for_user(u);
因为使用计数器,您不需要在更新前阅读余额。 http://www.datastax.com/dev/blog/whats-new-in-cassandra-0-8-part-2-counters
然而,计数器存在问题,它们不是幂等的,因此如果您没有收到确认您的增量/减量成功,则无法重播该计数器,因为它可能导致计数过多。
新计数器将解决problem。