使用Apache Ignite实现无死锁事务

死锁会杀死服务,我们看看Apache Ignite通过分配数字到事务是如何实现无死锁的事务的。

在多用户环境中,对于缓存内存中同一条数据或对象进行并发事务操作时会引发死锁,这是性能杀手,一旦系统进入严重的死锁状态,恢复需要整个集群重新启动,而Apache Ignite能够支持兼容ACID的无死锁事务,防止死锁和增强应用性能。

什么是死锁?
有两个并发事务T1和T2,T1等待资源R2释放,因为R2正在被事务T2锁住,而T2正在等待资源R1释放,而R1正在被事务T1锁住,彼此互相等待对方释放自己需要操作的资源,永远等下去了。

死锁是因为并发事务视图以不同顺序从同一对象上获得锁,解决办法是使用同样顺序获得锁,但是这种方法不灵活,不可控的。

Apache Ignite执行事务是使用并发模式 OPTIMISTIC和隔离级别SERIALIZABLE,锁是在提交事务commit期间获得,同时使用附加检查,这样能避免死锁,能提高吞吐量,甚至,在提交期间,如果Ignite探测到一个读写冲突或多个事务之间的锁冲突,只有一个事务被允许提交commit,所有其他冲突的事务将回滚且抛错。

那么其工作原理是什么?
Ignite会分配一个版本数字到每个事务和缓存中被操作的项目,版本数字会帮助决定一个事务将被提交或回滚,遭遇下面情况,Ignite会失败一个OPTIMISTIC(乐观) SERIALIZABLE(串行化) 事务(T2),并抛错TransactionOptimisticException :
1.如果存在正在进行的一个悲观事务,或者是乐观事务,但是带有隔离级别是READ-COMMITTED or REPEATABLE-READ,这个事务称为T1,它正在锁住缓存中一个项目,而该项目被T2请求,那么T2抛错。

2.存在另外一个正在进行的乐观串行化事务T1,它的版本号大于T2,而T2正在锁住缓存中项目将被T2请求加锁,那么T2将抛错。

3.当T2获得所有需要的锁,缓存中存在一个项目带有当前版本号不同于被观察版本号,这是因为另外一个事务T1已经提交了,改变了缓存项目的版本。

代码案例:


public class DeadlockExample {
private static final String ENTRY1 = "entry1";
private static final String ENTRY2 =
"entry2";
public static void main(String[] args) throws IgniteException {
Ignite ignite = Ignition.start(
"/myexamples/config/cluster-config.xml");
// Create cache with given name, if it does not exist.
final IgniteCache<String, String> cache = ignite.getOrCreateCache(
"myCache");
// populate
int i = 0;
cache.put(ENTRY1, Integer.toString(i++));
cache.put(ENTRY2, Integer.toString(i++));
new Thread(() -> {
try (Transaction t1 = Ignition.ignite().transactions().txStart(PESSIMISTIC, REPEATABLE_READ)) {
String val1 = cache.get(ENTRY1);
cache.put(ENTRY1, val1 +
"b");
String val2 = cache.get(ENTRY2);
cache.put(ENTRY2, val2 +
"b");
t1.commit();
System.out.println(
"t1: " + cache.get(ENTRY1));
System.out.println(
"t1: " + cache.get(ENTRY2));
}
},
"t1-Thread").start();
new Thread(() -> {
try (Transaction t2 = Ignition.ignite().transactions().txStart(OPTIMISTIC, SERIALIZABLE)) {
String val2 = cache.get(ENTRY2);
cache.put(ENTRY2, val2 +
"c");
String val1 = cache.get(ENTRY1);
cache.put(ENTRY1, val1 +
"c");
t2.commit();
System.out.println(
"t2: " + cache.get(ENTRY1));
System.out.println(
"t2: " + cache.get(ENTRY2));
}
},
"t2-Thread").start();
}
}

该案例输出T2回滚抛错Exception,因为事务T2和T1有锁冲突。

在一个高并发环境中,乐观锁会导致更频繁的事务失败,但是相比悲观锁导致死锁更可能发生还是有利的,Optimistic-Serializable (乐观-串行化)事务在Ignite中明显快于悲观事务,能够提供高性能,Ignite事务是一种ACID兼容机制,确保集群中数据一直一致,也就是高一致性。

Deadlock-Free Transactions With Apache Ignite - DZ