使用Apache Ignite實現無死鎖事務

banq發表於2016-09-22
死鎖會殺死服務,我們看看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();
    }
}
<p class="indent">


該案例輸出T2回滾拋錯Exception,因為事務T2和T1有鎖衝突。

在一個高併發環境中,樂觀鎖會導致更頻繁的事務失敗,但是相比悲觀鎖導致死鎖更可能發生還是有利的,Optimistic-Serializable (樂觀-序列化)事務在Ignite中明顯快於悲觀事務,能夠提供高效能,Ignite事務是一種ACID相容機制,確保叢集中資料一直一致,也就是高一致性。

Deadlock-Free Transactions With Apache Ignite - DZ

相關文章