數據庫、操做系統和編譯器並稱爲三大系統,能夠說是整個計算機軟件的基石。其中數據庫更靠近應用層,是不少業務的支撐。這一領域通過了幾十年的發展,不斷的有新的進展。
不少人用過數據庫,可是不多有人實現過一個數據庫,特別是實現一個分佈式數據庫。瞭解數據庫的實現原理和細節,一方面能夠提升我的技術,對構建其餘系統有幫助,另外一方面也有利於用好數據庫。
研究一門技術最好的方法是研究其中一個開源項目,數據庫也不例外。單機數據庫領域有不少很好的開源項目,其中 MySQL 和 PostgreSQL 是其中知名度最高的兩個,很多同窗都看過這兩個項目的代碼。可是分佈式數據庫方面,好的開源項目並很少。 TiDB 目前得到了普遍的關注,特別是一些技術愛好者,但願可以參與這個項目。因爲分佈式數據庫自身的複雜性,不少人並不能很好的理解整個項目,因此我但願能寫一些文章,自頂向上,由淺入深,講述 TiDB 的一些技術原理,包括用戶可見的技術以及大量隱藏在 SQL 界面後用戶不可見的技術點。算法
本篇爲本系列文章第一章數據庫
數據庫最根本的功能是能把數據存下來,因此咱們從這裏開始。
保存數據的方法不少,最簡單的方法是直接在內存中建一個數據結構,保存用戶發來的數據。好比用一個數組,每當收到一條數據就向數組中追加一條記錄。這個方案十分簡單,能知足最基本,而且性能確定會很好,可是除此以外倒是漏洞百出,其中最大的問題是數據徹底在內存中,一旦停機或者是服務重啓,數據就會永久丟失。
爲了解決數據丟失問題,咱們能夠把數據放在非易失存儲介質(好比硬盤)中。改進的方案是在磁盤上建立一個文件,收到一條數據,就在文件中 Append 一行。OK,咱們如今有了一個能持久化存儲數據的方案。可是還不夠好,假設這塊磁盤出現了壞道呢?咱們能夠作 RAID (Redundant Array of Independent Disks),提供單機冗餘存儲。若是整臺機器都掛了呢?好比出現了火災,RAID 也保不住這些數據。咱們還能夠將存儲改用網絡存儲,或者是經過硬件或者軟件進行存儲複製。到這裏彷佛咱們已經解決了數據安全問題,能夠鬆一口氣了。But,作複製過程當中是否能保證副本之間的一致性?也就是在保證數據不丟的前提下,還要保證數據不錯。保證數據不丟不錯只是一項最基本的要求,還有更多使人頭疼的問題等待解決:數組
做爲保存數據的系統,首先要決定的是數據的存儲模型,也就是數據以什麼樣的形式保存下來。TiKV 的選擇是 Key-Value 模型,而且提供有序遍歷方法。簡單來說,能夠將 TiKV 看作一個巨大的 Map,其中 Key 和 Value 都是原始的 Byte 數組,在這個 Map 中,Key 按照 Byte 數組總的原始二進制比特位比較順序排列。
你們這裏須要對 TiKV 記住兩點:安全
任何持久化的存儲引擎,數據終歸要保存在磁盤上,TiKV 也不例外。可是 TiKV 沒有選擇直接向磁盤上寫數據,而是把數據保存在 RocksDB 中,具體的數據落地由 RocksDB 負責。這個選擇的緣由是開發一個單機存儲引擎工做量很大,特別是要作一個高性能的單機引擎,須要作各類細緻的優化,而 RocksDB 是一個很是優秀的開源的單機存儲引擎,能夠知足咱們對單機引擎的各類要求,並且還有 Facebook 的團隊在作持續的優化,這樣咱們只投入不多的精力,就能享受到一個十分強大且在不斷進步的單機引擎。固然,咱們也爲 RocksDB 貢獻了一些代碼,但願這個項目能越作越好。這裏能夠簡單的認爲 RocksDB 是一個單機的 Key-Value Map。網絡
好了,萬里長征第一步已經邁出去了,咱們已經爲數據找到一個高效可靠的本地存儲方案。俗話說,萬事開頭難,而後中間難,最後結尾難。接下來咱們面臨一件更難的事情:如何保證單機失效的狀況下,數據不丟失,不出錯?簡單來講,咱們須要想辦法把數據複製到多臺機器上,這樣一臺機器掛了,咱們還有其餘的機器上的副本;複雜來講,咱們還須要這個複製方案是可靠、高效而且能處理副本失效的狀況。聽上去比較難,可是好在咱們有 Raft 協議。Raft 是一個一致性算法,它和 Paxos 等價,可是更加易於理解。這裏是 Raft 的論文,感興趣的能夠看一下。本文只會對 Raft 作一個簡要的介紹,細節問題能夠參考論文。另外提一點,Raft 論文只是一個基本方案,嚴格按照論文實現,性能會不好,咱們對 Raft 協議的實現作了大量的優化。
Raft 是一個一致性協議,提供幾個重要的功能:數據結構
到這裏咱們總結一下,經過單機的 RocksDB,咱們能夠將數據快速地存儲在磁盤上;經過 Raft,咱們能夠將數據複製到多臺機器上,以防單機失效。數據的寫入是經過 Raft 這一層的接口寫入,而不是直接寫 RocksDB。經過實現 Raft,咱們擁有了一個分佈式的 KV,如今不再用擔憂某臺機器掛掉了。併發
講到這裏,咱們能夠提到一個很是重要的概念:Region。這個概念是理解後續一系列機制的基礎,請仔細閱讀這一節。
前面提到,咱們將 TiKV 看作一個巨大的有序的 KV Map,那麼爲了實現存儲的水平擴展,咱們須要將數據分散在多臺機器上。這裏提到的數據分散在多臺機器上和 Raft 的數據複製不是一個概念,在這一節咱們先忘記 Raft,假設全部的數據都只有一個副本,這樣更容易理解。
對於一個 KV 系統,將數據分散在多臺機器上有兩種比較典型的方案:一種是按照 Key 作 Hash,根據 Hash 值選擇對應的存儲節點;另外一種是分 Range,某一段連續的 Key 都保存在一個存儲節點上。TiKV 選擇了第二種方式,將整個 Key-Value 空間分紅不少段,每一段是一系列連續的 Key,咱們將每一段叫作一個 Region,而且咱們會盡可能保持每一個 Region 中保存的數據不超過必定的大小(這個大小能夠配置,目前默認是 64MB)。每個 Region 均可以用 StartKey 到 EndKey 這樣一個左閉右開區間來描述。
mvc
注意,這裏的 Region 仍是和 SQL 中的表沒什麼關係!請各位繼續忘記 SQL,只談 KV。
將數據劃分紅 Region 後,咱們將會作兩件重要的事情:負載均衡
這兩點很是重要,咱們一點一點來講。分佈式
先看第一點,數據按照 Key 切分紅不少 Region,每一個 Region 的數據只會保存在一個節點上面。咱們的系統會有一個組件來負責將 Region 儘量均勻的散佈在集羣中全部的節點上,這樣一方面實現了存儲容量的水平擴展(增長新的節點後,會自動將其餘節點上的 Region 調度過來),另外一方面也實現了負載均衡(不會出現某個節點有不少數據,其餘節點上沒什麼數據的狀況)。同時爲了保證上層客戶端可以訪問所須要的數據,咱們的系統中也會有一個組件記錄 Region 在節點上面的分佈狀況,也就是經過任意一個 Key 就能查詢到這個 Key 在哪一個 Region 中,以及這個 Region 目前在哪一個節點上。至因而哪一個組件負責這兩項工做,會在後續介紹。
對於第二點,TiKV 是以 Region 爲單位作數據的複製,也就是一個 Region 的數據會保存多個副本,咱們將每個副本叫作一個 Replica。Repica 之間是經過 Raft 來保持數據的一致(終於提到了 Raft),一個 Region 的多個 Replica 會保存在不一樣的節點上,構成一個 Raft Group。其中一個 Replica 會做爲這個 Group 的 Leader,其餘的 Replica 做爲 Follower。全部的讀和寫都是經過 Leader 進行,再由 Leader 複製給 Follower。
你們理解了 Region 以後,應該能夠理解下面這張圖:
咱們以 Region 爲單位作數據的分散和複製,就有了一個分佈式的具有必定容災能力的 KeyValue 系統,不用再擔憂數據存不下,或者是磁盤故障丟失數據的問題。這已經很 Cool,可是還不夠完美,咱們須要更多的功能。
不少數據庫都會實現多版本控制(MVCC),TiKV 也不例外。設想這樣的場景,兩個 Client 同時去修改一個 Key 的 Value,若是沒有 MVCC,就須要對數據上鎖,在分佈式場景下,可能會帶來性能以及死鎖問題。
TiKV 的 MVCC 實現是經過在 Key 後面添加 Version 來實現,簡單來講,沒有 MVCC 以前,能夠把 TiKV 看作這樣的:
Key1 -> Value
Key2 -> Value
……
KeyN -> Value
有了 MVCC 以後,TiKV 的 Key 排列是這樣的:
Key1-Version3 -> Value
Key1-Version2 -> Value
Key1-Version1 -> Value
……
Key2-Version4 -> Value
Key2-Version3 -> Value
Key2-Version2 -> Value
Key2-Version1 -> Value
……
KeyN-Version2 -> Value
KeyN-Version1 -> Value
……
注意,對於同一個 Key 的多個版本,咱們把版本號較大的放在前面,版本號小的放在後面(回憶一下 Key-Value 一節咱們介紹過的 Key 是有序的排列),這樣當用戶經過一個 Key + Version 來獲取 Value 的時候,能夠將 Key 和 Version 構造出 MVCC 的 Key,也就是 Key-Version。而後能夠直接 Seek(Key-Version),定位到第一個大於等於這個 Key-Version 的位置。
TiKV 的事務採用的是 Percolator 模型,而且作了大量的優化。事務的細節這裏不詳述,你們能夠參考論文以及咱們的其餘文章。這裏只提一點,TiKV 的事務採用樂觀鎖,事務的執行過程當中,不會檢測寫衝突,只有在提交過程當中,纔會作衝突檢測,衝突的雙方中比較早完成提交的會寫入成功,另外一方會嘗試從新執行整個事務。當業務的寫入衝突不嚴重的狀況下,這種模型性能會很好,好比隨機更新表中某一行的數據,而且表很大。可是若是業務的寫入衝突嚴重,性能就會不好,舉一個極端的例子,就是計數器,多個客戶端同時修改少許行,致使衝突嚴重的,形成大量的無效重試。
到這裏,咱們已經瞭解了 TiKV 的基本概念和一些細節,理解了這個分佈式帶事務的 KV 引擎的分層結構以及如何實現多副本容錯。下一節會介紹如何在 KV 的存儲模型之上,構建 SQL 層。