RocksDB作為一個開源的存儲引擎支持事務(wù)的ACID特性,而要支持ACID中的I(Isolation),并發(fā)控制這塊是少不了的,本文主要討論RocksDB的鎖機制實現(xiàn),細節(jié)會涉及到源碼分析,希望通過本文讀者可以深入了解RocksDB并發(fā)控制原理。文章主要從以下4方面展開,首先會介紹RocksDB鎖的基本結(jié)構(gòu),然后我會介紹RocksDB行鎖數(shù)據(jù)結(jié)構(gòu)設(shè)計下,鎖空間開銷,接著我會介紹幾種典型場景的上鎖流程,最后會介紹鎖機制中必不可少的死鎖檢測機制。
1.行鎖數(shù)據(jù)結(jié)構(gòu)
RocksDB鎖粒度最小是行,對于KV存儲而言,鎖對象就是key,每一個key對應一個LockInfo結(jié)構(gòu)。所有key通過hash表管理,查找鎖時,直接通過hash表定位即可確定這個key是否已經(jīng)被上鎖。但如果全局只有一個hash表,會導致這個訪問這個hash表的沖突很多,影響并發(fā)性能。RocksDB首先按Columnfamily進行拆分,每個Columnfamily中的鎖通過一個LockMap管理,而每個LockMap再拆分成若干個分片,每個分片通過LockMapStripe管理,而hash表(std::unordered_map<std::string, LockInfo>)則存在于Stripe結(jié)構(gòu)中,Stripe結(jié)構(gòu)中還包含一個mutex和condition_variable,這個主要作用是,互斥訪問hash表,當出現(xiàn)鎖沖突時,將線程掛起,解鎖后,喚醒掛起的線程。這種設(shè)計很簡單但也帶來一個顯而易見的問題,就是多個不相關(guān)的鎖公用一個condition_variable,導致鎖釋放時,不必要的喚醒一批線程,而這些線程重試后,發(fā)現(xiàn)仍然需要等待,造成了無效的上下文切換。對比我們之前討論的InnoDB鎖機制,我們發(fā)現(xiàn)InnoDB是一個page里面的記錄復用一把鎖,而且復用是有條件的,同一個事務(wù)對一個page的若干條記錄加鎖才能復用;而且鎖等待隊列是精確等待,精確到記錄級別,不會導致的無效的喚醒。雖然RocksDB鎖設(shè)計比較粗糙,但也做了一定的優(yōu)化,比如在管理LockMaps時,通過在每個線程本地緩存一份拷貝lock_maps_cache_,通過全局鏈表將每個線程的cache鏈起來,當LockMaps變更時(刪除columnfamily),則全局將每個線程的copy清空,由于columnfamily改動很少,所以大部分訪問LockMaps操作都是不需要加鎖的,提高了并發(fā)效率。
相關(guān)數(shù)據(jù)結(jié)構(gòu)如下: