受this的启发,我在Cassandra 2.1.4上写了一个简单的互斥锁。
以下是锁定/解锁(伪)代码的外观:
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。但是,锁仍然存在(或重新出现)。
这是为什么?
现在我知道我可以很容易地在这个表上放置一个TTL(对于这个用例),但是如何可靠地删除该行呢?
答案 0 :(得分:2)
我对看到此代码的猜测是分布式系统编程中常见的错误。假设在失败的情况下,您尝试纠正失败将成功。
在上面的代码中,您检查以确保初始写入成功,但不要确保"回滚"也很成功。这可能会导致各种不需要的状态。
让我们想象一下副本A,B和C的一些场景。
客户端创建Lock但会引发错误。锁存在所有副本上,但客户端会因为该连接丢失或损坏而超时。
A[Lock], B[Lock], C[Lock]
我们在客户端上有一个例外,并尝试通过发出删除来撤消锁定,但这会在客户端返回异常时失败。这意味着系统可以处于各种状态。
A[Lock], B[Lock], C[Lock]
所有仲裁请求都将看到锁定。没有复制品的组合可以向我们显示Lock已被删除。
A[Lock], B[Lock], C[]
在这种情况下,我们仍然很脆弱。任何将C作为仲裁呼叫的一部分而排除的请求都将错过删除。如果仅轮询A和B,我们仍然会看到锁存在。
A[Lock/], B[], C[]
在这种情况下,我们再次失去了与驱动程序的连接,但在某种程度上成功地在内部复制了删除请求。这些场景是我们实际上安全的唯一场景,未来的读取将不会看到锁定。
在这种情况下,其中一个棘手的问题是,如果你失败了,由于网络不稳定而导致你的锁定正确,你的修正也不太可能成功,因为它必须在完全相同的环境中工作。
这可能是CAS操作有益的一个例子。但在大多数情况下,最好不要尝试使用分配锁定。