在 Cassandra 中丢失更新

Lost updates in Cassandra

我在更新 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 卡桑德拉版本:2.1.8

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

在大约 50 万笔交易中,一次特定的更新会失败。这种情况通常发生在用户快速连续完成两个事务时,时间可以缩短到几毫秒。这是日志:

Transaction #1

10 Feb 2016 18:15:16,984 -[pool-11-thread-1]-  INFO - ScratchpadMasterStreamProcessor.processMessage(62) - Printing str id: 1466140282Scratchpad id: 9127013322

10 Feb 2016 18:15:16,986 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Current balance: 0.0

10 Feb 2016 18:15:16,986 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Deviation : 200.0

10 Feb 2016 18:15:16,986 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Updating user..510978682

10 Feb 2016 18:15:16,987 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) - Final Balance: 200.0

10 Feb 2016 18:15:16,987 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Balance Update was successful for wallet 510978682

Transaction #2

10 Feb 2016 18:18:19,157 -[pool-11-thread-1]-  INFO - ConsumerThread.run(82) - Event Recieved

10 Feb 2016 18:18:19,159 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Current balance: 200.0

10 Feb 2016 18:18:19,159 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Deviation : 50.0

10 Feb 2016 18:18:19,159 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Updating user..510978682

10 Feb 2016 18:18:19,160 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) - Final Balance: 250.0

10 Feb 2016 18:18:19,160 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Balance Update was successful for wallet 510978682

Transaction #3 (This is lost)

10 Feb 2016 18:18:19,160 -[pool-11-thread-1]-  INFO - ScratchpadMasterStreamProcessor.processMessage(62) - Printing str id: 1466162182Scratchpad id: 9127117934

10 Feb 2016 18:18:19,161 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Current balance: 250.0

10 Feb 2016 18:18:19,161 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Deviation : -250.0

10 Feb 2016 18:18:19,161 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Updating user..510978682

10 Feb 2016 18:18:19,162 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) - Final Balance: 0.0

10 Feb 2016 18:18:19,162 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Balance Update was successful for wallet 510978682

Transaction #4 Read stale balance, oops

10 Feb 2016 18:18:23,140 -[pool-11-thread-1]-  INFO - ConsumerThread.run(82) - Event Recieved

10 Feb 2016 18:18:23,140 -[pool-11-thread-1]-  INFO - ScratchpadMasterStreamProcessor.processMessage(62) - Printing str id: 1466162730Scratchpad id: 9127120830

10 Feb 2016 18:18:23,141 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(43) - Current balance: 250.0

10 Feb 2016 18:18:23,141 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(44) - Deviation : 200.0

10 Feb 2016 18:18:23,141 -[pool-11-thread-1]- DEBUG - UserBalanceManager.updateWalletBalance(70) - Updating user..510978682

10 Feb 2016 18:18:23,142 -[pool-11-thread-1]- DEBUG - SclwBalanceUpdater.updateBalance(51) - Final Balance: 450.0

10 Feb 2016 18:18:23,142 -[pool-11-thread-1]- DEBUG - ScratchpadMasterStreamProcessor.processMessage(79) - Balance Update was successful for wallet 510978682

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

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

不过,我想提出一个解决方案,而无需转移到其他数据库。 您可以为 balance 字段使用计数器类型。 counter 的更新语句的工作方式不同。它向 cassandra 命令发送到 increase/decrease 该字段按一定的值,所以你不会有不一致的问题。

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

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

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

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

Cassandra 非常擅长不可变数据和幂等操作。与交易或频繁 updates/deletes.

关系不大

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

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