【问题标题】:Lost updates in CassandraCassandra 中的更新丢失
【发布时间】:2016-05-26 00:40:32
【问题描述】:

在 Cassandra 中更新一行时,我遇到了丢失写入的问题。这是我的架构:

create table balances(
id bigint,
balance decimal,
last_transaction_id bigint,
update_timestamp timestamp,
type varchar,
is_balance_valid boolean, 
primary key (wallet_id)
) 

集群中的节点总数:本地 DC 中的 3 个 复制因子:2 Cassandra 版本:2.1.8

每次用户进行交易时,我都会通过读取先前设置的值、添加交易金额并发布更新来更新“余额”列的值。我正在使用 Java、Datastax 驱动程序 (2.1.5)。

在大约 50 万笔交易中,有一次特定更新会失败。这通常发生在用户快速连续完成两个事务时,低至几毫秒。以下是日志:

交易#1

2016 年 2 月 10 日 18:15:16,984 -[pool-11-thread-1]-  信息 - ScratchpadMasterStreamProcessor.processMessage(62) - 打印 str id: 1466140282便签本号:9127013322

2016 年 2 月 10 日 18:15:16,986 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(43) - 当前余额:0.0

2016 年 2 月 10 日 18:15:16,986 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(44) - 偏差:200.0

2016 年 2 月 10 日 18:15:16,986 -[pool-11-thread-1]- 调试 - UserBalanceManager.updateWalletBalance(70) - 更新用户..510978682

2016 年 2 月 10 日 18:15:16,987 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(51) - 最终余额:200.0

2016 年 2 月 10 日 18:15:16,987 -[pool-11-thread-1]- 调试 - ScratchpadMasterStreamProcessor.processMessage(79) - 余额更新 钱包510978682成功

交易 #2

2016 年 2 月 10 日 18:18:19,157 -[pool-11-thread-1]-  信息 - ConsumerThread.run(82) - 收到事件

2016 年 2 月 10 日 18:18:19,159 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(43) - 当前余额:200.0

2016 年 2 月 10 日 18:18:19,159 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(44) - 偏差:50.0

2016 年 2 月 10 日 18:18:19,159 -[pool-11-thread-1]- 调试 - UserBalanceManager.updateWalletBalance(70) - 更新用户..510978682

2016 年 2 月 10 日 18:18:19,160 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(51) - 最终余额:250.0

2016 年 2 月 10 日 18:18:19,160 -[pool-11-thread-1]- 调试 - ScratchpadMasterStreamProcessor.processMessage(79) - 余额更新 钱包510978682成功

事务 #3(已丢失)

2016 年 2 月 10 日 18:18:19,160 -[pool-11-thread-1]-  信息 - ScratchpadMasterStreamProcessor.processMessage(62) - 打印 str id: 1466162182便签本编号:9127117934

2016 年 2 月 10 日 18:18:19,161 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(43) - 当前余额:250.0

2016 年 2 月 10 日 18:18:19,161 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(44) - 偏差:-250.0

2016 年 2 月 10 日 18:18:19,161 -[pool-11-thread-1]- 调试 - UserBalanceManager.updateWalletBalance(70) - 更新用户..510978682

2016 年 2 月 10 日 18:18:19,162 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(51) - 最终余额:0.0

2016 年 2 月 10 日 18:18:19,162 -[pool-11-thread-1]- 调试 - ScratchpadMasterStreamProcessor.processMessage(79) - 余额更新 钱包510978682成功

事务 #4 读取过期余额,哎呀

2016 年 2 月 10 日 18:18:23,140 -[pool-11-thread-1]-  信息 - ConsumerThread.run(82) - 收到事件

2016 年 2 月 10 日 18:18:23,140 -[pool-11-thread-1]-  信息 - ScratchpadMasterStreamProcessor.processMessage(62) - 打印 str id: 1466162730便签本编号:9127120830

2016 年 2 月 10 日 18:18:23,141 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(43) - 当前余额:250.0

2016 年 2 月 10 日 18:18:23,141 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(44) - 偏差:200.0

2016 年 2 月 10 日 18:18:23,141 -[pool-11-thread-1]- 调试 - UserBalanceManager.updateWalletBalance(70) - 更新用户..510978682

2016 年 2 月 10 日 18:18:23,142 -[pool-11-thread-1]- 调试 - SclwBalanceUpdater.updateBalance(51) - 最终余额:450.0

2016 年 2 月 10 日 18:18:23,142 -[pool-11-thread-1]- 调试 - ScratchpadMasterStreamProcessor.processMessage(79) - 余额更新 钱包510978682成功

我已将读取和写入的一致性级别设置为 LOCAL_QUORUM,并且三个 cassandra 节点服务器具有相同的时间(使用 NTP)。可能是什么问题?

【问题讨论】:

  • Cassandra 等数据库在扩展、分发到多个数据中心和可用性方面具有显着优势。但这是以降低一致性保证为代价的。这很可能是你正在经历的。如果在所有情况下都需要精确平衡,我不确定 Cassandra 是否是正确的选择。

标签: java cassandra updates datastax consistency


【解决方案1】:

Cassandra 非常擅长不可变数据和幂等操作。与事务或频繁更新/删除无关。

一个快速检查是您是否使用轻量级交易。它们以性能为代价,但在重要数据中可能是必需的。例如,

UPDATE balances
SET balance = <new_balance>
WHERE id = <wallet_id>
IF balance = <old_balance>

【讨论】:

    【解决方案2】:

    首先,请看一下 Codo 的评论,该评论很好地描述了为什么你有问题。

    但是,我想提出一个解决方案,而无需转移到其他数据库。 您可以为 balance 字段使用计数器类型。 counter 的更新语句的工作方式不同。它发送到 cassandra 命令以按特定值增加/减少该字段,因此您不会遇到不一致的问题。

    然而,基于计数器的解决方案并不适用于所有应用程序。例如,它仅限于整数类型。可能更常见的解决方案是以编程方式创建一种事务:将更新请求保存在单独的表中并创建异步过程,该过程聚合在特定时间段内完成的所有更新请求并将它们应用于balance 值。

    【讨论】:

    • 其实余额是浮点数,所以使用计数器是行不通的。对于第二点,这意味着在阅读时,我必须在两个表中进行查找,并保留一个标记来跟踪尚未聚合的事务。要么我删除了很多行,要么我的第二个表变得很大,我认为这两者都没有用,因为读取速度会变慢。
    • 只需将其乘以 100 并将其存储为 int(例如以美分而不是美元)。如果这不符合您的要求,请阅读我的建议的第二部分。
    • 嗯,我确实要求 balance 的值始终正确。其次,使用计数器也不能保证防止过时的读取link
    【解决方案3】:

    您似乎有更新操作的竞争条件。 Cassandra 不会更新数据,而是插入一个带有另一个时间戳的新数据。最后一个时间戳的数据是数据的实际版本。

    您是否为更新查询指定时间戳?

    时间戳允许您为突变操作提供正确的序列。 如果你不指定 Transaction#3 的时间戳结果,它可以被 Transaction#2 覆盖,因为它们是快速连续完成的。

    【讨论】:

      猜你喜欢
      • 2015-05-23
      • 2013-06-08
      • 2019-09-12
      • 2015-09-24
      • 1970-01-01
      • 1970-01-01
      • 2015-05-14
      • 1970-01-01
      • 2020-09-07
      相关资源
      最近更新 更多