TiKV 的 MVCC(Multi-Version Concurrency Control)機制
併發控制簡介
事務隔離在資料庫系統中有著非常重要的作用,因為對於使用者來說資料庫必須提供這樣一個“假象”:當前只有這麼一個使用者連線到了資料庫中,這樣可以減輕應用層的開發難度。但是,對於資料庫系統來說,因為同一時間可能會存在很多使用者連線,那麼許多併發問題,比如資料競爭(data race),就必須解決。在這樣的背景下,資料庫管理系統(簡稱 DBMS)就必須保證併發操作產生的結果是安全的,通過可序列化(serializability)來保證。
雖然 Serilizability 是一個非常棒的概念,但是很難能夠有效的實現。一個經典的方法就是使用一種兩段鎖(2PL)。通過 2PL,DBMS 可以維護讀寫鎖來保證可能產生衝突的事務按照一個良好的次序(well-defined) 執行,這樣就可以保證 Serializability。但是,這種通過鎖的方式也有一些缺點:
- 讀鎖和寫鎖會相互阻滯(block)。
- 大部分事務都是隻讀(read-only)的,所以從事務序列(transaction-ordering)的角度來看是無害的。如果使用基於鎖的隔離機制,而且如果有一段很長的讀事務的話,在這段時間內這個物件就無法被改寫,後面的事務就會被阻塞直到這個事務完成。這種機制對於併發效能來說影響很大。
多版本併發控制(Multi-Version Concurrency Control, 以下簡稱 MVCC)以一種優雅的方式來解決這個問題。在 MVCC 中,每當想要更改或者刪除某個資料物件時,DBMS 不會在原地去刪除或這修改這個已有的資料物件本身,而是建立一個該資料物件的新的版本,這樣的話同時併發的讀取操作仍舊可以讀取老版本的資料,而寫操作就可以同時進行。這個模式的好處在於,可以讓讀取操作不再阻塞,事實上根本就不需要鎖。這是一種非常誘人的特型,以至於在很多主流的資料庫中都採用了 MVCC 的實現,比如說 PostgreSQL,Oracle,Microsoft SQL Server 等。
TiKV 中的 MVCC
讓我們深入到 TiKV 中的 MVCC,瞭解 MVCC 在 TiKV 中是如何實現的。
Timestamp Oracle(TSO)
因為TiKV
是一個分散式的儲存系統,它需要一個全球性的授時服務,下文都稱作 TSO(Timestamp Oracle),來分配一個單調遞增的時間戳。 這樣的功能在 TiKV 中是由 PD 提供的,在 Google 的 Spanner 中是由多個原子鐘和 GPS 來提供的。
Storage
從原始碼結構上來看,想要深入理解 TiKV 中的 MVCC 部分,src/storage 是一個非常好的入手點。 Storage
是實際上接受外部命令的結構體。
pub struct Storage {
engine: Box<Engine>,
sendch: SendCh<Msg>,
handle: Arc<Mutex<StorageHandle>>,
}
impl Storage {
pub fn start(&mut self, config: &Config) -> Result<()> {
let mut handle = self.handle.lock().unwrap();
if handle.handle.is_some() {
return Err(box_err!("scheduler is already running"));
}
let engine = self.engine.clone();
let builder = thread::Builder::new().name(thd_name!("storage-scheduler"));
let mut el = handle.event_loop.take().unwrap();
let sched_concurrency = config.sched_concurrency;
let sched_worker_pool_size = config.sched_worker_pool_size;
let sched_too_busy_threshold = config.sched_too_busy_threshold;
let ch = self.sendch.clone();
let h = try!(builder.spawn(move || {
let mut sched = Scheduler::new(engine,
ch,
sched_concurrency,
sched_worker_pool_size,
sched_too_busy_threshold);
if let Err(e) = el.run(&mut sched) {
panic!("scheduler run err:{:?}", e);
}
info!("scheduler stopped");
}));
handle.handle = Some(h);
Ok(())
}
}
start
這個函式很好的解釋了一個 storage 是怎麼跑起來的。
Engine
首先是 Engine。 Engine
是一個描述了在儲存系統中接入的的實際上的資料庫的介面,raftkv 和 Enginerocksdb 分別實現了這個介面。
StorageHandle
StorageHanle
是處理從sench
接受到指令,通過 mio 來處理 IO。
接下來在Storage
中實現了async_get
和async_batch_get
等非同步函式,這些函式中將對應的指令送到通道中,然後被排程器(scheduler)接收到並非同步執行。
Ok,瞭解完Storage
結構體是如何實現的之後,我們終於可以接觸到在Scheduler
被呼叫的 MVCC 層了。
當 storage 接收到從客戶端來的指令後會將其傳送到排程器中。然後排程器執行相應的過程或者呼叫相應的非同步函式。在排程器中有兩種操作型別,讀和寫。讀操作在 MvccReader 中實現,這一部分很容易理解,暫且不表。寫操作的部分是MVCC的核心。
MVCC
Ok,兩段提交(2-Phase Commit,2PC)是在 MVCC 中實現的,整個 TiKV 事務模型的核心。在一段事務中,由兩個階段組成。
Prewrite
選擇一個 row 作為 primary row, 餘下的作為 secondary row。 對primary row 上鎖. 在上鎖之前,會檢查是否有其他同步的鎖已經上到了這個 row 上 或者是是否經有在 startTS 之後的提交操作。這兩種情況都會導致衝突,一旦都衝突發生,就會回滾(rollback)。 對於 secondary row 重複以上操作。
Commit
Rollback 在Prewrite
過程中出現衝突的話就會被呼叫。
Garbage Collector
很容易發現,如果沒有垃圾收集器(Gabage Collector) 來移除無效的版本的話,資料庫中就會存有越來越多的 MVCC 版本。但是我們又不能僅僅移除某個 safe point 之前的所有版本。因為對於某個 key 來說,有可能只存在一個版本,那麼這個版本就必須被儲存下來。在TiKV
中,如果在 safe point 前存在Put
或者Delete
,那麼說明之後所有的 writes 都是可以被移除的,不然的話只有Delete
,Rollback
和Lock
會被刪除。
TiKV-Ctl for MVCC
在開發和 debug 的過程中,我們發現查詢 MVCC 的版本資訊是一件非常頻繁並且重要的操作。因此我們開發了新的工具來查詢 MVCC 資訊。TiKV
將 Key-Value,Locks 和Writes 分別儲存在CF_DEFAULT
,CF_LOCK
,CF_WRITE
中。它們以這樣的格式進行編碼
default | lock | write | |
---|---|---|---|
key | z{encoded_key}{start_ts(desc)} | z{encoded_key} | z{encoded_key}{commit_ts(desc)} |
value | {value} | {flag}{primary_key}{start_ts(varint)} | {flag}{start_ts(varint)} |
Details can be found here.
因為所有的 MVCC 資訊在 Rocksdb 中都是儲存在 CF Key-Value 中,所以想要查詢一個 Key 的版本資訊,我們只需要將這些資訊以不同的方式編碼,隨後在對應的 CF 中查詢即可。CF Key-Values 的表示形式。
相關文章
- MongoDB的鎖機制 (Concurrency)MongoDB
- Mysql MVCC機制MySqlMVC
- MySQL鎖機制與MVCCMySqlMVC
- MySQL中的MVCC實現機制MySqlMVC
- PostgreSQL MVCC快照機制淺析SQLMVC
- MySQL 學習筆記(二)MVCC 機制MySql筆記MVC
- TiKV 原始碼解析系列文章(十三)MVCC 資料讀取原始碼MVC
- MySQL多版本併發控制——MVCC機制分析MySqlMVC
- 【MySQL(5)| 五分鐘搞清楚 MVCC 機制】MySqlMVC
- YaoBase基於MVCC的無鎖化樂觀併發機制MVC
- MySQL多版本併發控制機制(MVCC)-原始碼淺析MySqlMVC原始碼
- 一文讀懂MySQL的事務隔離級別及MVCC機制MySqlMVC
- 資料訪問模式:資料併發控制(Data Concurrency Control)模式
- MySQL的事務機制和鎖(InnoDB引擎、MVCC多版本併發控制技術)MySqlMVC
- PostgreSQL的MVCC vs InnoDB的MVCCSQLMVC
- Mysql鎖機制與最佳化實踐以及MVCC底層原理剖析MySqlMVC
- PostgreSQl的MVCCSQLMVC
- HDFS 02 - HDFS 的機制:副本機制、機架感知機制、負載均衡機制負載
- MVCCMVC
- Concurrency Patterns in GoGo
- tikv oom排查過程OOM
- 國內外雲平臺訪問控制(Access Control)機制對比分析
- [Mysql]MVCCMySqlMVC
- Java Concurrency in Depth - 1Java
- Java Concurrency in Depth - 2Java
- TiDB3.0.1與3.0.2版本的TiKV當機對比測試TiDB
- SCN的機制
- TiDB3.0.2版本某業務TiKV當機測試TiDB
- Percolator模型及其在TiKV中的實現模型
- TiKV 原始碼解析系列 – Raft 的優化原始碼Raft優化
- TiKV 原始碼解析系列 - Raft 的優化原始碼Raft優化
- SeaweedFS + TiKV 部署保姆級教程
- session機制和cookie機制SessionCookie
- 對Innodb中MVCC的理解MVC
- 響應式流的核心機制——背壓機制
- MVCC與鎖MVC
- Concurrency(十五: Java中的讀寫鎖)Java
- 塗鴉智慧選型 TiKV 的心路歷程