JanusGraph的鎖機制

PS:簡書的markdown與筆記格式不太一樣,原文請戳這里



一、鎖的實現方式

涉及的類:

Locker -> AbstractLocker -> ConsistentKeyLocker

LocalLockMediator

1. Locker接口

文檔中對這個接口的解釋:

該接口代表線程安全的任意的鎖,可能是在JanusGraph進程中的,也可能是在多個進程之間的。Lock本身就是一個確認了的KeyColumn的實例。如果兩個KeyColumn使用了同一個Lock,則這兩個KeyColumn調用equals方法應該返回true。

獲取鎖的線程是通過StoreTransaction來確認的。

該接口使用三步鎖模型來獲取鎖,支持阻塞鎖和非阻塞鎖。不過不是所有的StoreTransaction都需要鎖并使用這個接口。如果StoreTransaction需要一個或者多個鎖,JanusGraph會按照以下順序調用接口中的方法(tx代表每一步中的同一個StoreTransaction):

writeLock(kc, tx) 使用tx事務對象獲取對某個KeyColumn的鎖。每個KeyColumn對象都調用一次或多次。

checkLocks(tx) 檢查當前的事務之前調用的writeLock方法實際上有沒有成功。如果用戶提交事務或者用戶放棄了這個事務時,會調用一次。

deleteLocks(tx) 釋放當前事務所持有的鎖。不論checkLocks方法有沒有調用,這個方法都會調用一次。

2. AbstractLocker接口:

Doc中的解釋:

構建Locker的抽象基礎類。實現了線程之間的鎖(使用LocakLockMediator)。進程之間的鎖由子類來實現。

AbstractLocker中實現了Locker接口中的方法。針對這些實現的方法,又添加了幾個新的方法由子類實現(例如,Locker中是writeLock方法,AbstractLocker類中,又添加了writeSingleLock方法,此方法在writeLock中被調用)。而實現的Locker接口中的方法都需要調用新定義的這幾個方法。

publicabstractclassAbstractLocker<SextendsLockStatus>implementsLocker{abstractSwriteSingleLock(KeyColumnlockID,StoreTransactiontx);abstractvoid checkSingleLock(KeyColumnlockID,SlockStatus,StoreTransactiontx);abstractvoid deleteSingleLock(KeyColumnlockID,SlockStatus,StoreTransactiontx);}

3. ConsistentKeyLocker的使用

ConsistentKeyLocker實現了AbstractLocker中定義的方法。

API doc的介紹

ConsistentKeyLocker是一個全局的鎖,通過AbstractLocker中的方法實現了線程之間的鎖的競爭。并通過使用KeyColumnValueStore來進行數據的讀和寫實現了進程之間的鎖的競爭。

加鎖的過程如下:

加鎖是通過兩個階段來完成的:首先在進程之間的線程中獲取鎖,然后再JanusGraph Cluster的多個進程之間來獲取鎖。

階段一:線程之間的鎖的競爭

在一個共享的進程中,線程的鎖的競爭是由LocalLockMeditator來“仲裁(原文是arbitrated)”的。這個仲裁者使用concurrent包中的類確保對于任何一個KeyColumn,在任何時間段內最多只有一個線程持有他的鎖。這段代碼是在AbstractLocker中。

階段二:進程之間的鎖的競爭

如果在階段一中,meditator發出信號表示當前線程持有了進程內的鎖,則接下來會通過讀和寫KeyColumnValueStore來檢查是否當前進程是唯一持有該鎖的進程。在Cassandra或者HBase中,都有專用的store來專門存儲鎖數據(BigTable模型中,store一般表示column family)。

進程之間競爭鎖所涉及的IO操作:

寫一個column來存儲key(KeyColumn.getKey()+KeyColumn.getValue()), column(大致的時間戳+進程的rid)和value(0的byte表示)。

如果寫失敗了或者超過了LockWait的實現,就重試寫操作,帶一個修改的時間戳,直到超出了配置的重試次數(獲取失敗)或者在LockWait的時間內完成了寫操作(但寫操作成功了不代表獲取鎖成功了)。

如果需要的話,執行等待,直到lockWait的時間耗盡,或者寫成功。

獲取這個key的所有的column。

擦除掉timestamp已經過了lockExpire的記錄。

如果我們的column是第一個讀的column,或者只有當前的rid持有該column(階段一的meditator保證了一個KeyColumn在一個進程中只有一個線程能獲取到鎖),則獲取鎖成功。否則失敗。

釋放鎖時,刪除掉記錄中的該條column(注意不是整個key都刪掉)。

結合代碼:

publicvoidwriteLock(KeyColumn lockID, StoreTransaction tx)throwsTemporaryLockingException, PermanentLockingException{? ? .....//調用meditator先贏得線程之間的鎖的競爭。if(lockLocally(lockID, tx)){booleanok =false;try{//往HBase中寫一條ColumnS stat = writeSingleLock(lockID, tx);//修改local lock的過期時間lockLocally(lockID, stat.getExpirationTimestamp(), tx);// lockState是記錄鎖狀態的緩存lockState.take(tx, lockID, stat);? ? ? ? ? ? ok =true;? ? ? ? }catch(){//對異常的處理}finally{if(!ok){? ? ? ? ? ? ? ? unlockLocally(lockID, tx);? ? ? ? ? ? ? ? ....? ? ? ? ? ? }? ? ? ? }? ? }else{thrownewPermanentLockingException("Local lock contention");? ? }}

4. LocalLockMediator的實現:

LocalLockMediator是用于在同一個JVM之間的事務競爭鎖而使用的。其底層是基于一個ConcurrentHashMap的putIfAbsent。putIfAbsent返回一個Map中的Value,這個value如果是null,代表ConcurrentHashMap中原本不存在這個key。但如果返回不是null,則放棄put操作。當key不存在時,putIfAbsent成功了就是獲取鎖成功了。

二、 Lock的使用場景:

在Backend類中,有一個ConcurrentHashMap用于保存Locker:

publicclassBackendimplementsLockerProvider,AutoCloseable{? ? privatefinalFunction lockerCreator;? ? privatefinalConcurrentHashMap lockers =newConcurrentHashMap<>();@Overridepublic Locker getLocker(StringlockerName) {? ? ? ? Preconditions.checkNotNull(lockerName);? ? ? ? Locker l = lockers.get(lockerName);if(null== l) {? ? ? ? ? ? l = lockerCreator.apply(lockerName);finalLocker x = lockers.putIfAbsent(lockerName, l);if(null!= x) {? ? ? ? ? ? ? ? l = x;? ? ? ? ? ? }? ? ? ? }returnl;? ? }}

而Backend中的getLocker方法最終所使用的地方則是ExpectedValueCheckingStore。

三、ExpectedValueChecking家族

ExpectedValueChecking家族是指以ExpectedValueChecking開頭的幾個類。分別包括:

ExpectedValueCheckingStore

ExpectedValueCheckingStoreManager

ExpectedValueCheckingTransaction

ExpectedValueCheckingStore:

該類是KeyColumnValueStore的一個包裝類。KeyColumnValueStore是一個接口,該接口表示的是針對BigTable模型的數據操作接口。該接口提供了讀和寫數據的方法。

ExpectedValueCheckingStore主要是對KeyColumnValueStore中的mutate方法和acquireLock方法進行了封裝,其內部有一個KeyColumnValueStore變量,在這兩個方法前后又加了一些邏輯,其余的方法都是直接調用KeyColumnValueStore的對應方法。其最終仍然需要依賴一個內部的KeyColumnValueStore變量。

這個類一般是跟ExpectedValueCheckingTransaction一起,為每個StoreTransaction跟蹤所有的傳入acquireLock方法中的expectedValue參數。當事務調用mutate方法時,這些類會協同一起檢查所有之前提供的expected value是否匹配實際的值。如果不匹配拋出異常。

//對KeyColumnValueStore的基礎代理類,所實現的繼承的方法都是直接調用其內部的store變量來實現。publicclassKCVSProxyimplementsKeyColumnValueStore{? ? ? ? protectedfinal KeyColumnValueStore store;}publicclassExpectedValueCheckingStoreextendsKCVSProxy{finalLocker locker;//對象的初始化需要使用代理的KeyColumnValueStore,以及locker。publicExpectedValueCheckingStore(KeyColumnValueStore store, Locker locker){super(store);this.locker = locker;? ? }// (1)確認事務txh之前調用acquireLock獲取鎖是否成功// (2)如果成功了就將additions和deletetions寫到底層存儲的key中// (3) Deletions是在additions之前執行的。也就是說,如果某個column既有deletion也有addition,會先刪除,然后添加。//如果實現類中不支持鎖,則跳過鎖的認證階段,并執行后來的階段。publicvoidmutate(StaticBuffer key, List<Entry> additions, List<StaticBuffer> deletions, StoreTransaction txh)throwsBackendException{? ? ? ? .....? ? }//試圖獲取key value對所聲明的鎖。鎖是隨機分配的。//如果鎖獲取失敗了,可以拋出PermanentLockingException。但這并不是強制的,如果獲取鎖失敗了,也可以不拋出異常。鎖的獲取只要在KeyColumnValueStore調用mutate方法時確認獲取成功即可。// expectedValue必須匹配key value代表的publicvoidacquireLock(StaticBuffer key, StaticBuffer column, StaticBuffer expectedValue, StoreTransaction txh)throwsBackendException{? ? ? ? ....? ? }}

ExpectedValueCheckingTransaction

ExpectedValueCheckingTransaction是一個StoreTransaction的實現類。支持通過LocalLockMediator來獲取鎖,在ExpectedValueCheckingSotre中用于讀和寫鎖記錄。

其父類StoreTransaction代表一個事務的把手,用于唯一標識后端存儲的一個事務。所有對后端的修改操作必須有一個單個的事務作為context。這樣的事務能被JanusGraph中間件識別出來就是通過StoreTransaction。圖的事務依賴于底層存儲的事務。

要注意StoreTransaction本身不提供任何的隔離以及一致性的保證。如果對應的后端支持的話,Graph Transaction必須自己去實現。

在這個類中,有一個強一致的事務和一個不一致的事務。

publicclassExpectedValueCheckingTransactionimplementsStoreTransaction{//在事務鎖的階段一致是false。事務中調用mutate或者mutateMany方法開始時設置為true。privatebooleanisMutationStarted;//用于對鎖相關的元數據進行讀和寫的事務。privatefinalStoreTransaction strongConsistentTx;//用于讀和寫客戶端數據的事務。不保證強一致性。privatefinalStoreTransaction inconsistentTx;}

Backend的初始化

Backend中,關鍵的元數據的初始化如下,以HBase為例:

publicclassBackendimplementsLockerProvider,AutoCloseable{//HBaseStoreManager。Backend中有多個KeyColumnValueStore的初始化,是通過該變量來實現的。這些變量各自負責hbase的一個column family的操作(所以,在mutate或者mutateMany方法中,是沒有column family的)。privatefinalKeyColumnValueStoreManager storeManager;//ExpectedKeyColumnValueStoreManagerprivatefinalKeyColumnValueStoreManager storeManagerLocking;privatefinalMap stores;privatefinalConcurrentHashMap lockers =newConcurrentHashMap<>();//構造方法。構造方法調用完之后調用initialize方法。publicBackend(Configuration config){? ? ? ? storeManager = ...;? ? ? ? storeManagerLocking =newExpectedValueCheckingStoreManager(storeManager,LOCK_STORE_SUFFIX,this,maxReadTime);? ? ? ? ? ? }publicvoidinitialize(){//使用ExpectedValueCheckingStoreManager來創建ExpectedValueCheckingStore。創建的對象包括://IDAuthority idAuthority;//KCVSCache edgeStore//KCVSCache indexStore//KCVSCache txLogStore//KCVSConfiguration systemConfig//KCVSConfiguration userConfig}publicLockergetLocker(String lockerName){//創建ConsistentKeyLocker。}}

總結:

這里的設計完美的提現了面向接口編程,代理模式。

KeyColumnValueStore的實現類:HbaseKeyColumnValueStore ExpectedValueCheckingStore其中使用了HBaseKeyColumnValueStore,作為代理,添加了調用writeLock的方法保證了進程之間只有一個線程能獲取KeyColumn的鎖。

KeyColumnValueStoreManager用于創建KeyColumnValueStore(openDatebase)方法。各個不同的存儲后端都需要實現mutateMany方法用于將緩存中的內容持久化到DB中。其實現類是HBaseStoreManager。

在Backend創建systemConfig和userConfig時,會創建ExpectedValueCheckingStoreTransaction(通過ExpectedValueCheckingStoreManager.beginTransaction方法)。ExpectedValueCheckingStoreTransaction中使用的兩個StoreTransaction最終都是HBaseTransaction類。至少從HBase的角度來講,這里兩個變量的設計似乎略顯多余,因為HBase不是強一致的,由于CAP定理的限制,HBase采用的是最終一致性。

關于ExpectedValueCheckingTransaction如何檢查expectedValue的,可以查看checkSingleExpectedValueUnsafe這個私有方法。

ExpectedValueCheckingStore在執行mutate方法時,會調用該檢查。

```

@Override

public void mutate(StaticBuffer key, List<Entry> additions, List<StaticBuffer> deletions, StoreTransaction txh) throws BackendException {

? ? ExpectedValueCheckingTransaction etx = (ExpectedValueCheckingTransaction)txh;

? ? // 這一步會對expectedValue做檢查。如果檢查失敗會拋異常。

? ? boolean hasAtLeastOneLock = etx.prepareForMutations();

? ? if (hasAtLeastOneLock) {

? ? ? ? // Force all mutations on this transaction to use strong consistency

? ? ? ? store.mutate(key, additions, deletions, getConsistentTx(txh));

? ? } else {

? ? ? ? store.mutate(key, additions, deletions, unwrapTx(txh));

? ? }

}

```

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。