Cassandra 数据重现
Cassandra data reappearing
受到this的启发,我在Cassandra 2.1.4上写了一个简单的互斥量。
下面是 lock/unlock(伪)代码的样子:
public boolean lock(String uuid){
try {
Statement stmt = new SimpleStatement("INSERT INTO LOCK (id) VALUES (?) IF NOT EXISTS", uuid);
stmt.setConsistencyLevel(ConsistencyLevel.QUORUM);
ResultSet rs = session.execute(stmt);
if (rs.wasApplied()) {
return true;
}
} catch (Throwable t) {
Statement stmt = new SimpleStatement("DELETE FROM LOCK WHERE id = ?", uuid);
stmt.setConsistencyLevel(ConsistencyLevel.QUORUM);
session.execute(stmt); // DATA DELETED HERE REAPPEARS!
}
return false;
}
public void unlock(String uuid) {
try {
Statement stmt = new SimpleStatement("DELETE FROM LOCK WHERE id = ?", uuid);
stmt.setConsistencyLevel(ConsistencyLevel.QUORUM);
session.execute(stmt);
} catch (Throwable t) {
}
}
现在,我可以随意重现在高负载测试中 lock() 中抛出 WriteTimeoutException 的情况。这意味着数据 may or may not be written。在此之后,我的代码删除了锁 - 并再次抛出 WriteTimeoutException。但是,锁定仍然存在(或重新出现)。
这是为什么?
现在我知道我可以轻松地在此 table 上设置 TTL(对于此用例),但我如何可靠地删除该行?
我看到这段代码的猜测是分布式系统编程中发生的常见错误。假设万一失败,您纠正失败的尝试将会成功。
在上面的代码中,您检查以确保初始写入成功,但不确保“回滚”也成功。这可能会导致各种不想要的状态。
让我们想象几个副本 A、B 和 C 的场景。
客户端创建锁但抛出错误。锁存在于所有副本上,但客户端超时,因为连接丢失或中断。
系统状态
A[Lock], B[Lock], C[Lock]
我们在客户端发生异常,并尝试通过发出删除来撤消锁定,但失败并在客户端返回异常。这意味着系统可以处于多种状态。
0 次成功写入删除
A[Lock], B[Lock], C[Lock]
所有仲裁请求都会看到锁。不存在可以向我们显示锁已被移除的副本组合。
1 次成功写入删除
A[Lock], B[Lock], C[]
在这种情况下,我们仍然很脆弱。任何将 C 排除在仲裁调用之外的请求都将错过删除。如果只有 A 和 B 被轮询,那么我们仍然会看到锁存在。
2/3 成功写入删除(满足仲裁 CL)
A[Lock/], B[], C[]
在这种情况下,我们再次失去了与驱动程序的连接,但不知何故在内部成功地复制了删除请求。这些场景是我们真正安全且未来读取不会看到锁的唯一场景。
结论
在这种情况下,棘手的事情之一是,如果您由于网络不稳定而无法正确锁定,那么您的更正也不太可能成功,因为它必须在完全相同的环境中工作。
这可能是 CAS 操作有益的一个例子。但在大多数情况下,如果可能的话,最好不要尝试使用分布式锁定。
受到this的启发,我在Cassandra 2.1.4上写了一个简单的互斥量。
下面是 lock/unlock(伪)代码的样子:
public boolean lock(String uuid){
try {
Statement stmt = new SimpleStatement("INSERT INTO LOCK (id) VALUES (?) IF NOT EXISTS", uuid);
stmt.setConsistencyLevel(ConsistencyLevel.QUORUM);
ResultSet rs = session.execute(stmt);
if (rs.wasApplied()) {
return true;
}
} catch (Throwable t) {
Statement stmt = new SimpleStatement("DELETE FROM LOCK WHERE id = ?", uuid);
stmt.setConsistencyLevel(ConsistencyLevel.QUORUM);
session.execute(stmt); // DATA DELETED HERE REAPPEARS!
}
return false;
}
public void unlock(String uuid) {
try {
Statement stmt = new SimpleStatement("DELETE FROM LOCK WHERE id = ?", uuid);
stmt.setConsistencyLevel(ConsistencyLevel.QUORUM);
session.execute(stmt);
} catch (Throwable t) {
}
}
现在,我可以随意重现在高负载测试中 lock() 中抛出 WriteTimeoutException 的情况。这意味着数据 may or may not be written。在此之后,我的代码删除了锁 - 并再次抛出 WriteTimeoutException。但是,锁定仍然存在(或重新出现)。
这是为什么?
现在我知道我可以轻松地在此 table 上设置 TTL(对于此用例),但我如何可靠地删除该行?
我看到这段代码的猜测是分布式系统编程中发生的常见错误。假设万一失败,您纠正失败的尝试将会成功。
在上面的代码中,您检查以确保初始写入成功,但不确保“回滚”也成功。这可能会导致各种不想要的状态。
让我们想象几个副本 A、B 和 C 的场景。
客户端创建锁但抛出错误。锁存在于所有副本上,但客户端超时,因为连接丢失或中断。
系统状态
A[Lock], B[Lock], C[Lock]
我们在客户端发生异常,并尝试通过发出删除来撤消锁定,但失败并在客户端返回异常。这意味着系统可以处于多种状态。
0 次成功写入删除
A[Lock], B[Lock], C[Lock]
所有仲裁请求都会看到锁。不存在可以向我们显示锁已被移除的副本组合。
1 次成功写入删除
A[Lock], B[Lock], C[]
在这种情况下,我们仍然很脆弱。任何将 C 排除在仲裁调用之外的请求都将错过删除。如果只有 A 和 B 被轮询,那么我们仍然会看到锁存在。
2/3 成功写入删除(满足仲裁 CL)
A[Lock/], B[], C[]
在这种情况下,我们再次失去了与驱动程序的连接,但不知何故在内部成功地复制了删除请求。这些场景是我们真正安全且未来读取不会看到锁的唯一场景。
结论
在这种情况下,棘手的事情之一是,如果您由于网络不稳定而无法正确锁定,那么您的更正也不太可能成功,因为它必须在完全相同的环境中工作。
这可能是 CAS 操作有益的一个例子。但在大多数情况下,如果可能的话,最好不要尝试使用分布式锁定。