2

问题:

我计划将 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

现在,这里有几个问题:

  1. 与 cassandra 的连接可能会中断,导致代码仅更新少数用户的余额。
  2. 在第 2 步和第 4 步之间,可能有另一个过程可以更新用户的余额,我将更新一个过时的余额,使用户的余额处于“损坏”状态。

RDBMS 解决了这些问题,因为它们提供了 ACID 属性,而 Cassandra 没有。我看到 Cassandra 最近(2012 年 10 月)开始提供 Atomic Batches。我不确定这是否是解决此问题的正确方法。

可能的解决方案:

这是我和一个朋友的头脑风暴。我们实际上并没有更新用户的余额,而是创建一个记录,将更新增量附加到不同的记录。例如:

UID1_balance = {100}
UID1_deltas  = {10,20,-40}

为了获得当前余额,我们只需将增量应用于余额。我们可以有一个离线过程,将增量应用到用户的余额并修剪增量列表。

该解决方案有效并减少了损坏状态的可能性,但我认为这是一种矫枉过正的做法。有没有更好的方法来解决这个问题?

4

3 回答 3

6

我建议阅读“建立在流沙上”的论文,这会让你从账户的角度思考,他们甚至参考了这样的银行账户示例。注意:大通银行和富国银行不会在交易中转账,所以他在那篇文章中解释了我们如何在微观层面上做同样的事情,就像宏观层面一样;)。

这在为 cassandra 编写 PlayOrm 时也很有帮助,因为现在 PlayOrm wiki 上也有一个模式页面。

于 2013-02-18T02:48:14.057 回答
2

由于没有锁,因此您最初的“读取修改写入”方法无法在 Cassandra 中使用。Cassandra 计数器部分解决了这个问题,但在两个地方无法满足您的要求:

  1. 您不能使用多个计数器进行原子批处理,这意味着您最终可能会应用一些更新而其他更新不会。
  2. 如果发生错误,您无法判断计数器是否增加了,因此最终可能会出现不准确的值。

这意味着您将增量存储为单独列的可能解决方案是获得所需保证的唯一方法,与 Cassandra 1.2 中的原子批处理一起使用(请参阅http://www.datastax.com/dev/blog/atomic- cassandra-1-2 中的批次)。您的解决方案就像实现计数器,其中每个计数器位于一行中,每个增量是一列。要阅读,您将一行中的列的所有值相加。

正如您所说,这里的问题是处理垃圾,因为这些增量列表会随着时间的推移而增长。如果没有太多更新没关系,但如果经常更新余额,阅读速度就会太慢。

通过读取增量然后原子地删除它们并为整个值添加一个增量,可以使您的“垃圾收集”离线过程变得安全。使用原子批处理和单线程进程可以确保安全。

于 2013-02-17T10:48:30.810 回答
2
  1. 正如理查德指出的那样,目前最好的方法是使用原子批处理来更新许多增量。如果出现问题,只需重播批处理。

  2. 另一种可能的解决方案是使用 ZooKeeper 作为协调和分布式锁定服务:http ://ria101.wordpress.com/tag/zookeeper/

  3. 另一种可能的解决方案是使用计数器,所以你不需要这样做

    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

但是计数器存在问题,它们不是幂等的,因此如果您没有收到您的增量/减量成功的确认,您将无法重放该计数器,因为它可能导致过度计数。

新的柜台将解决这个问题

于 2013-02-17T21:22:05.053 回答