PS:簡書的markdown與筆記格式不太一樣,原文請戳這里
一、鎖的實現方式
涉及的類:
Locker -> AbstractLocker -> ConsistentKeyLocker
LocalLockMediator
文檔中對這個接口的解釋:
該接口代表線程安全的任意的鎖,可能是在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方法有沒有調用,這個方法都會調用一次。
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);}
ConsistentKeyLocker實現了AbstractLocker中定義的方法。
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");? ? }}
LocalLockMediator是用于在同一個JVM之間的事務競爭鎖而使用的。其底層是基于一個ConcurrentHashMap的putIfAbsent。putIfAbsent返回一個Map中的Value,這個value如果是null,代表ConcurrentHashMap中原本不存在這個key。但如果返回不是null,則放棄put操作。當key不存在時,putIfAbsent成功了就是獲取鎖成功了。
在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開頭的幾個類。分別包括:
ExpectedValueCheckingStore
ExpectedValueCheckingStoreManager
ExpectedValueCheckingTransaction
該類是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中,關鍵的元數據的初始化如下,以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));
? ? }
}
```