尽管复制因子为 3 的 QUORUM 一致性级别,Cassandra 仍不一致

Posted

技术标签:

【中文标题】尽管复制因子为 3 的 QUORUM 一致性级别,Cassandra 仍不一致【英文标题】:Cassandra is not consistent despite QUORUM consistency level with replication factor 3 【发布时间】:2015-09-25 23:26:34 【问题描述】:

我对 Cassandras 的一致性有疑问。我在集群中有 3 个 Cassandra 节点(版本 2.0.14.352),我正在使用 consistency level QUORUM 进行读写,而我的 replicationfactor 是 3。 如果我理解this,就我而言,Cassandra 应该是一致的,因为 2+2>3。但是我用 java 写了一个测试,我使用 datastax-driver 非常快地将一些数据插入到 cassandra:

final Instant t1 = Instant.parse("2000-01-01T00:00:00.000Z");
final Instant t2 = Instant.parse("2000-02-01T00:00:00.000Z");

for (int i = 0; i < 100; i++) 
    dataProvider.setValue(t1, new Double(1));
    //If the next line is removed, the test will pass
    dataProvider.setValue(t2, new Double(3));

    dataProvider.saveToDB();
    dataProvider.clear();
    assertEquals("i=" + i, new Double(3), dataProvider.getValue(t2));
    assertEquals("i=" + i, new Double(1), dataProvider.getValue(t1));

    dataProvider.setValue(t1, new Double(2));
    dataProvider.saveToDB();
    dataProvider.clear();
    assertEquals("i=" + i, new Double(2), dataProvider.getValue(t1));

    dataProvider.setValue(t1, new Double(101));
    dataProvider.saveToDB();
    dataProvider.clear();
    assertEquals("i=" + i, new Double(101), dataProvider.getValue(t1));

对应的表格

CREATE TABLE keyspace.table(
  id text,
  year int,
  month int,
  time timestamp,
  value double,
  PRIMARY KEY ((id, year, month), time)
)

dataProvider.setValue() 在内部将给定值放入 NavigableMap。 dataProvider.saveToDB() 将数据插入 Cassandra。在这里,我一方面尝试异步插入数据并等待所有 ResultSetFuture 完成,另一方面我同步执行语句。但这仅影响性能。详细的保存方法看起来像

final List<ResultSetFuture> sets = newLinkedList();
Batch batch = QueryBuilder.batch();
int batchsize=0;
for (Map.Entry<Instant, Double> entry : valueMap) 
    final Instant instant = entry.getKey();
    final ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("UTC"));
    final Date date = Date.from(instant);
    final Insert insert = QueryBuilder.insertInto(table)
            .value(ID, id)
            .value(YEAR, zonedDateTime.getYear())
            .value(MONTH, zonedDateTime.getMonthValue())
            .value(TIME, date)
            .value(VALUE, entry.getValue());
    batch.add(insert);
    ++batchsize;
    if(batchsize % 200 == 0)
        sets.add(cassandraConnector.executeAsync(batch));
        batch = QueryBuilder.batch();
    

if(batchsize % 200 != 0)  //es gibt noch nicht abgeschickte Statements
    sets.add(cassandraConnector.executeAsync(batch));

cassandraConnector.waitForFinish(sets);

cassandraConnector 管理连接。我正在等到所有 ResultSets 完成

public boolean waitForFinish(List<ResultSetFuture> sets) 
    ResultSet result = null;
    for (final ResultSetFuture resultSetFuture : sets) 
        // Wait until finished
        try 
            result = resultSetFuture.get();
         catch (InterruptedException e) 
            resultSetFuture.cancel(true);
            e.printStackTrace();
            return false;
         catch (ExecutionException e) 
            e.printStackTrace();
            if (result != null) 
                ExecutionInfo executionInfo = result.getExecutionInfo();
                System.out.println("Timout from server with IP: " + executionInfo.getTriedHosts());
            
            return false;
        
    
    return true;

好奇的是,如果我删除注释下的行,测试就会通过,而我执行它的频率并不重要。但是,如果我在不删除该行的情况下运行测试,有时它会在第一个循环中失败,但有时它会运行 3 个循环,直到失败。此外,它总是在不同的线路上失败。例如

java.lang.AssertionError: i=0 
Expected :101
Actual   :2

我也有

java.lang.AssertionError: i=2
Expected :2
Actual   :101

所以似乎是 Cassandra 写了 1,之后 Cassandra 没有写 2,而是恢复了我在 1 之前写的 101。有人对这种行为有解释吗?如果我删除线,为什么测试会通过?我正在写不同的分区。我尝试将一致性级别更改为 ALL,但行为没有改变。

【问题讨论】:

能否也展示一下DataProvider类的代码? 我添加了更多代码。在那里你可以看到我是如何创建和执行语句的。 这看起来像是常见的批处理误解***.com/questions/30317877/… 但是如果我忽略批处理并同步执行每个插入,结果是一样的。此外,在测试中,批次仅包含 2 个插入,它们将 1 个值插入到 2 个不同的分区。完成此操作后,我会覆盖这些值。 我尝试根据您的讲述重新实现DataProvider 类,但这里的测试还可以。 clear 方法有什么作用?您如何运行代码以实现“非常快速地插入一些数据”的目标? 【参考方案1】:

我解决了。显然,时钟不是 100% 同步的。当我创建插入语句时,我添加了 .using(timestamp(System.nanoTime() / 1000)); 现在测试通过了。

【讨论】:

以上是关于尽管复制因子为 3 的 QUORUM 一致性级别,Cassandra 仍不一致的主要内容,如果未能解决你的问题,请参考以下文章

Cassandra 身份验证失败:“无法执行身份验证:无法达到一致性级别 QUORUM”

无主复制系统-Quorum一致性的局限性

Cassandra 读写一致性(Consistency)详解

在运行时确定 Cassandra 集群的复制策略

Cassandra 读取间歇性失败

CL Quorum 是不是可以进行脏读?