天天看點

TiKV讀寫流程淺析

1.TiKV架構圖和子產品說明

TiKV讀寫流程淺析

 圖1  TiKV整體架構圖

1.1.各子產品說明

PD Cluster:它是由多個PD節點組成的etcd叢集,PD是具有“上帝視角”的管理元件,負責存儲中繼資料和進行負載均衡,比如Region對應的range段資訊、排程Region切分和合并等;

gRPC:開源遠端過程調用系統,用戶端服務端可基于該協定進行請求通信;

Placement Driver:管理TiKV叢集,管理着整個叢集的中繼資料資訊,負責檢查資料一緻性和資料自動平衡遷移;

TiKV node:用來存儲鍵值對的節點;

TxnKV API:支援事務操作的API;

RawKV API:不保證事務的的API;

Raft:一緻性算法,TiKV叢集使用了該算法來同步節點資料;

RocksDB:TiKV的真實後端存儲元件,RocksDB本身是個開源的鍵值對存儲系統;

Region:鍵值對資料移動的基本機關,每個region被複制到多個Nodes;

Raft group:多個同Region就組成一個Raft group,比如圖中不同顔色的Region,同顔色的就組成一個Raft group;

Leader:每個Raft gorup會有個Leader,負責處理用戶端的請求讀或寫,請求會先到Leader節點,再由Leader節點通知從節點修改。

1.2.功能特性

(1)多副本資料和資料自動均衡;

(2)容錯和資料恢複;

(3)支援設定key的過期時間;

(4)支援原子性的CAS(compare-and-swap);

(5)支援分布式事務;

2.TiKV工作流程原理

2.1. 分布式事務

TiKV的事務模型是使用Percolator Transaction model。

該事務模型依賴于一個時間戳服務,我們稱它為timestamp oracle,它會定時預先配置設定一個範圍時間戳,并且會将最大的那個時間戳儲存到磁盤上,然後即可在記憶體中遞增産生範圍内的時間戳給請求,這樣即使該時間戳服務當機了,下一次它預配置設定的也會從之前在磁盤上儲存的那個最高時間戳後開始進行預配置設定,保證配置設定的時間戳永遠是不會回退的。該時間戳服務是嵌入到PD服務裡的,由PD leader進行服務。

Percolator最先是應用在google的BigTable項目上的,它是一個支援單行事務的分布式存儲系統。

Percolator有CF(column family )的概念,類似于Rocksdb中的CF,每個CF會對應一個LSM Tree,但共享與一個WAL。

Percolator有5個CF,分别是lock、data、write、notify和ack;

Tikv裡隻涉及到前面3個,這裡隻講述前面3個。

當開啟一個事務寫入一個 key-value 的時候:

Prewrite階段(兩階段中的第一階段):

lock CF:将該key的lock放到lock CF;

data CF:将該key對應的value放到data CF;

Commit階段(兩階段中的第二階段):

write CF:将相對應的commit資訊放到write CF;

寫資料過程:

在送出資料時采用兩階段送出。

Prewrite階段:

(1)擷取事務的開始時間戳start_ts;

(2)将事務涉及到的多個資料在lock CF中進行寫入,寫入時會檢查是否該資料是否已經被其它事務鎖住,如果是則進行復原;并且從多個資料中選擇一個作為primary lock,其它的使用secondary lock,secondary lock裡包含了primary對應資料的資訊;

(3)将新資料寫入到data CF中,同時在寫入時也需要檢查該資料是否有大于start_ts時間戳的事務更新送出,如果有則表示有沖突,需要進行復原

如果Prewrite階段沒有沖突,則Prewrite階段成功,進入Commit階段。

Commit階段:

(1)擷取commit時間戳commit_ts;

(2)對primary的資料進行寫入,将commit資訊寫入到write CF中,從lock CF中移除該資料的primary lock;

(3)對Secondary的資料進行寫入,類似primary一樣的操作;

注意:commit階段當完成了第(2)步的primary資料的commit後就表示這個事務已經成功了,即使第(3)步的Secondary的資料commit失敗了也不影響整個事務表示成功,是以commit階段當完成了前兩步就向用戶端表示事務成功,第(3)是使用異步步的方式進行commit的。原因是Prewrite階段上鎖成功表示commit階段不會有沖突問題,是以一般都會成功,但有一種情況會失敗,那就是比如發生了類似當機這樣的事情,那麼此時Secondary鎖還會在lock CF裡,是以其它事務在檢測該資料鎖時是判斷不了該資料事務是否已送出的,它可以通過Secondary lock擷取到之前Primary的資訊,然後去查找對應的Primary資料是否commit成功,如果是則表示該事務已經送出成功了,繼續執行,如果Primary lock也還存在還未送出則上鎖失敗。

舉例:

假設Bob有10元,Joe有2元,現在Bob要轉7元給Joe。

TiKV讀寫流程淺析

 圖2

如上圖,這裡涉及到兩個key,Bob和Joe,它們目前key情況是最新送出是6,對應的data值是10和2。

現在進行轉賬操作,進行資料送出,那麼在Prewrite階段,就要進行lock CF寫入和data CF寫入,寫入後如下圖:

TiKV讀寫流程淺析

圖3

可以看到Bob該key被選中為Primary,并且data裡都寫上了經過轉賬後的值,分别為3和9,并且前面的7為申請的start_ts。

Prewrite階段順利,進入commit階段,先primary的key進行commit,commit後的情況如下圖:

TiKV讀寫流程淺析

 圖4

可以看到Bob的primary lock已經移除,Joe的還沒commit。

Joe的也進行commit得到如下圖所示:

TiKV讀寫流程淺析

 圖5

讀過程:

假設以圖5為例,讀取Bob key的值。

(1)首先也需要申請一個start_ts;

(2)然後在lock CF上搜尋Bob key在[0, start_ts]上有沒有被上鎖,如果有則讀取失敗待會進行重試,可以看到Bob key上是沒有鎖的;

(3)從write中擷取[0, start_ts]最新的寫事務送出值,取到了該key最新寫事務的start_ts,從圖中可以看到是7;

(4)通過7去擷取data CF上的start_ts為7時的值,從圖中可知是3,擷取成功并傳回。

TiKV中的Percolator跟上述講的類似,不過它的CF是defautl、lock和write,default對應的是上面所描述的data。同時也做了一些優化。

從上面過程中,我們看到在寫入data時,是将key和start_ts一起寫入的,start_ts是一個8bytes的值,會将它用大端序表示并進行取反(最新事務start_ts比舊事務start_ts大,取反後就會比舊start_ts小了),這樣做的目的是因為rocksdb存儲的LSM Tree的key是按序存放的,是以相同的key的不同版本會是相鄰的且最新的事務的key排在前面,這樣在查找最新事務送出時就會最先找到。

優化點:

(1)一個事務多個keys的Prewrite會分發到多個tikv節點進行并發預寫,當有一個失敗時,則進行復原;

(2)對于比較小的value,在兩階段送出時,資料最後不放入data CF裡,而是直接存放到write CF裡,這樣就不用先在write CF找,然後再在data CF裡找,隻需要一個LSM Tree的查找;

(3)如果事務隻讀取單個key,沒必要擷取start_ts,直接從write CF裡讀取最新版本的送出;

(4)由于單個Region的多個key的寫入是原子方式寫入的,是以對于一個事務,如果涉及的寫入的key都是在同一個Region的話,就可以不使用兩階段送出方式寫入了,直接1階段送出寫入。

2.2. 寫入流程

在TiKV中,Region是儲存key的基本單元,client端在讀寫資料時,都會先從pd中擷取指定key對應的Region資訊,比如Region對應的leader tikv節點,然後向該節點發起請求,同時該Region的資訊也會被緩存下來可用來加速後續的同樣在該Region的key的讀寫。

非事務的寫入流程圖:

TiKV讀寫流程淺析

非事務寫入流程:

(1)client端擷取操作的key所在的Region資訊;

(2)PD傳回該key所在Region的資訊,包括Region對應的TiKV Leader節點資訊等;

(3)向TiKV服務發起寫請求;

(4)由于Region是一個Raft group,這期間會進行一個Raft協定共識,會讓該Region的followers節點也收到該記錄檔(把操作當做一個日志,進行日志複制,應用時解析該日志進行執行),收到半數以上回複時即Leader節點應用該日志并回複用戶端,且在下一次心跳時告訴用戶端應用該日志;

(5)回複處理結果。

非事務的讀取流程圖:

TiKV讀寫流程淺析

非事務讀取流程:

(1)向PD擷取Region的資訊;

(2)傳回Region資訊;

(3)請求TiKV節點讀取key值;

(4)傳回key值資訊;

事務寫請求圖:

TiKV讀寫流程淺析

事務寫請求流程:

注意,這裡圖中隻寫了事務中隻有一個key更改的情況,沒有代表性,流程裡講時會加入b也更改,且與a在不同的Region。

(1)開啟事務擷取事務start_ts;

(2)client端擷取a和b的Region資訊,假設a對應Region1,b對應Region2,那麼在Prewrite階段,client端會并行分别向這兩個Region節點發送寫請求進行預寫,同時參數裡會帶上start_ts和Primary或Secondary,預寫入的過程就類似于上面寫的Percolator的Prewrite的過程,這裡以key a為例講述具體寫入CF的過程,假設申請的start_ts是10,key b也是一樣的,如果兩者有一個Prewrite階段失敗,那麼就是失敗,進行復原操作;

首先是寫lock CF:

lock CF:W a = Primary

然後是data CF:

data CF:a_10 = new_value

(3)當key a和b的Prewrite都成功的情況下進行Commit階段;

(4)申請commit_ts,假設是11;

(5)此時client端隻會先向key a的Region1發起commit請求(因為它是Primary lock),然後就是Percolator的commit階段

寫write CF,它的值是start_ts:

write CF:a_11 = 10

(6)key a的commit成功則向使用者傳回事務成功了,然後再異步送出key b的commit,這裡key b就算失敗也無礙的原因在Percolator裡說過了,本質就是其實CF裡都已經記錄下最新值的修改了,隻是Secondary的lock沒有移除掉。

事務的讀請求流程圖:

TiKV讀寫流程淺析

從Leader Region節點讀取值。

2.3.Range劃分

tikv的range是一種按照key位元組序進行排序的可看做是無限的sorted map,如果将該range按指定的點進行切分成多段range,那麼每段range就是一個region;

tikv初始隻有一個region,可以記為["", ""),region遵循左閉右開,比如如果使用key為abc1對該region進行切分則會得到兩個region:

region1:["", "abc")

region2:["abc", "")

TiKV讀寫流程淺析

預設配置下,一個Region的儲存上限大小是96M,當Region儲存的資料大于96M時,就會進行Region自動切分,分成均衡的兩個Region。

配置參數:region-split-size

TiKV在4.0版本引入了Load Base Split特性,該特性是用來解決Region熱點問題,當大量請求都打到一個Region時,由于一個Region的讀寫都是由一個節點上的Leader進行處理的,導緻大部分請求由一個節點處理,會造成瓶頸,該功能特性的原理是基于統計的資訊進行判斷,如果某個Region 10s内的qps或流量超過了配置檔案中指定的值,則會對其進行Region拆分,并被排程配置設定到不同的節點上以打散熱點Region。

相關配置參數:

QPS門檻值參數:split.qps-threshold

流量門檻值參數:split.byte-threshold

除了分裂,Region也會進行合并操作,避免有大量的空Region存在,造成大量的通信和管理開銷。

系統會定時的去輪詢檢測所有Region,如果Region的大小大于max-merge-region-size配置值(預設20M),則不會與相鄰的Region進行合并;如果Region的key的數量大于max-merge-region-keys配置值(預設

200000個)則不會與相鄰的Region進行合并;否則其它情況都會與相鄰的Region進行合并。

繼續閱讀