天天看點

java mysql 并發鎖_使用JAVA實作高并發無鎖資料庫操作步驟分享

1. 并發中如何無鎖。一個很簡單的思路,把并發轉化成為單線程。Java的Disruptor就是一個很好的例子。如果用java的concurrentCollection類去做,原理就是啟動一個線程,跑一個Queue,并發的時候,任務壓入Queue,線程輪訓讀取這個Queue,然後一個個順序執行。

在這個設計模式下,任何并發都會變成了單線程操作,而且速度非常快。現在的node.js, 或者比較普通的ARPG服務端都是這個設計,“大循環”架構。

這樣,我們原來的系統就有了2個環境:并發環境 + ”大循環“環境

并發環境就是我們傳統的有鎖環境,性能低下。

"大循環"環境是我們使用Disruptor開辟出來的單線程無鎖環境,性能強大。

2. ”大循環“環境 中如何提升處理性能。一旦并發轉成單線程,那麼其中一個線程一旦出現性能問題,必然整個處理都會放慢。是以在單線程中的任何操作絕對不能涉及到IO處理。那資料庫操作怎麼辦?

增加緩存。這個思路很簡單,直接從記憶體讀取,必然會快。至于寫、更新操作,采用類似的思路,把操作送出給一個Queue,然後單獨跑一個Thread去一個個擷取插庫。這樣保證了“大循環”中不涉及到IO操作。

問題再次出現:

如果我們的遊戲隻有個大循環還容易解決,因為裡面提供了完美的同步無鎖。

但是實際上的遊戲環境是并發和“大循環”并存的,即上文的2種環境。那麼無論我們怎麼設計,必然會發現在緩存這塊上要出現鎖。

3. 并發與“大循環”如何共處,消除鎖?我們知道如果在“大循環”中要避免鎖操作,那麼就用“異步”,把操作交給線程處理。結合這2個特點,我稍微改下資料庫架構。

原本的緩存層,必然會存在着鎖,例如:

public TableCache

{

private HashMap caches = new ConcurrentHashMap();

}

這個結構是必然的了,保證了在并發的環境下能夠準确的操作緩存。但是”大循環“卻不能直接操作這個緩存進行修改,是以必須啟動一個線程去更新緩存,例如:

private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();

EXECUTOR.execute(new LatencyProcessor(logs));

class LatencyProcessor implements Runnable

{

public void run()

{

// 這裡可以任意的去修改記憶體資料。采用了異步。

}

}

OK,看起來很漂亮。但是又有個問題出現了。在高速存取的過程中,非常有可能緩存還沒有被更新,就被其他請求再次擷取,得到了舊的資料。

4. 如何保證并發環境下緩存資料的唯一正确?我們知道,如果隻有讀操作,沒有寫操作,那麼這個行為是不需要加鎖的。

我使用這個技巧,在緩存的上層,再加一層緩存,成為”一級緩存“,原來的就自然成為”二級緩存“。有點像CPU了對不?

一級緩存隻能被”大循環“修改,但是可以被并發、”大循環“同時擷取,是以是不需要鎖的。

當發生資料庫變動,分2種情況:

1)并發環境下的資料庫變動,我們是允許有鎖的存在,是以直接操作二級緩存,沒有問題。

2)”大循環“環境下資料庫變動,首先我們把變動資料存儲在一級緩存,然後交給異步修正二級緩存,修正後删除一級緩存。

這樣,無論在哪個環境下讀取資料,首先判斷一級緩存,沒有再判斷二級緩存。

這個架構就保證了記憶體資料的絕對準确。

而且重要的是:我們有了一個高效的無鎖空間,去實作我們任意的業務邏輯。

最後,還有一些小技巧提升性能。

1. 既然我們的資料庫操作已經被異步處理,那麼某個時間,需要插庫的資料可能很多,通過對表、主鍵、操作類型的排序,我們可以删除一些無效操作。例如:

a)同一個表同一個主鍵的多次UPdate,取最後一次。

b)同一個表同一個主鍵,隻要出現Delete,前面所有操作無效。

2. 既然我們要對操作排序,必然會存在一個根據時間排序,如何保證無鎖呢?使用

private final static AtomicLong _seq = new AtomicLong(0);

即可保證無鎖又全局唯一自增,作為時間序列。