成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

TiDB 源碼閱讀系列文章(十九)tikv-client(下)

whataa / 1085人閱讀

摘要:是什么在介紹的概念之前,我們需要簡單回顧一下前面源碼閱讀系列文章六中講過的和的概念以及它們和語句的關(guān)系。的任務(wù)就是實(shí)現(xiàn)請(qǐng)求,執(zhí)行所有涉及到的請(qǐng)求,并依次返回結(jié)果。構(gòu)造出了所有之后,下一步就是執(zhí)行這些了。

上篇文章 中,我們介紹了數(shù)據(jù)讀寫過程中 tikv-client 需要解決的幾個(gè)具體問題,本文將繼續(xù)介紹 tikv-client 里的兩個(gè)主要的模塊——負(fù)責(zé)處理分布式計(jì)算的 copIterator 和執(zhí)行二階段提交的 twoPhaseCommitter。

copIterator copIterator 是什么

在介紹 copIterator 的概念之前,我們需要簡單回顧一下前面 TiDB 源碼閱讀系列文章(六)中講過的 distsql 和 coprocessor 的概念以及它們和 SQL 語句的關(guān)系。

tikv-server 通過 coprocessor 接口,支持部分 SQL 層的計(jì)算能力,大部分只涉及單表數(shù)據(jù)的常用的算子都可以下推到 tikv-server 上計(jì)算,計(jì)算下推以后,從存儲(chǔ)引擎讀取的數(shù)據(jù)雖然是一樣的多,但是通過網(wǎng)絡(luò)返回的數(shù)據(jù)會(huì)少很多,可以大幅節(jié)省序列化和網(wǎng)絡(luò)傳輸?shù)拈_銷。

distsql 是位于 SQL 層和 coprocessor 之間的一層抽象,它把下層的 coprocessor 請(qǐng)求封裝起來對(duì)上層提供一個(gè)簡單的 Select 方法。執(zhí)行一個(gè)單表的計(jì)算任務(wù)。最上層的 SQL 語句可能會(huì)包含 JOIN,SUBQUERY 等復(fù)雜算子,涉及很多的表,而 distsql 只涉及到單個(gè)表的數(shù)據(jù)。一個(gè) distsql 請(qǐng)求會(huì)涉及到多個(gè) region,我們要對(duì)涉及到的每一個(gè) region 執(zhí)行一次 coprocessor 請(qǐng)求。

所以它們的關(guān)系是這樣的,一個(gè) SQL 語句包含多個(gè) distsql 請(qǐng)求,一個(gè) distsql 請(qǐng)求包含多個(gè) coprocessor 請(qǐng)求。

copIterator 的任務(wù)就是實(shí)現(xiàn) distsql 請(qǐng)求,執(zhí)行所有涉及到的 coprocessor 請(qǐng)求,并依次返回結(jié)果。

構(gòu)造 coprocessor task

一個(gè) distsql 請(qǐng)求需要處理的數(shù)據(jù)是一個(gè)單表上的 index scan 或 table scan,在 Request 包含了轉(zhuǎn)換好的 KeyRange list。接下來,通過 region cache 提供的 LocateKey 方法,我們可以找到有哪些 region 包含了一個(gè) key range 范圍內(nèi)的數(shù)據(jù)。

找到所有 KeyRange 包含的所有的 region 以后,我們需要按照 region 的 range 把 key range list 進(jìn)行切分,讓每個(gè) coprocessor task 里的 key range list 不會(huì)超過 region 的范圍。

構(gòu)造出了所有 coprocessor task 之后,下一步就是執(zhí)行這些 task 了。

copIterator 的執(zhí)行模式

為了更容易理解 copIterator 的執(zhí)行模式,我們先從最簡單的實(shí)現(xiàn)方式開始, 逐步推導(dǎo)到現(xiàn)在的設(shè)計(jì)。

copIterator 是 kv.Response 接口的實(shí)現(xiàn),需要實(shí)現(xiàn)對(duì)應(yīng) Next 方法,在上層調(diào)用 Next ?的時(shí)候,返回一個(gè) coprocessor response,上層通過多次調(diào)用 Next 方法,獲取多個(gè) coprocessor response,直到所有結(jié)果獲取完。

最簡單的實(shí)現(xiàn)方式,是在 Next 方法里,執(zhí)行一個(gè) coprocessor task,返回這個(gè) task 的執(zhí)行結(jié)果。

這個(gè)執(zhí)行方式的一個(gè)很大的問題,大量時(shí)間耗費(fèi)在等待 coprocessor 請(qǐng)求返回結(jié)果,我們需要改進(jìn)一下。

coprocessor 請(qǐng)求如果是由 Next 觸發(fā)的,每次調(diào)用 Next 就必須等待一個(gè) RPC ?round trip 的延遲。我們可以改造成請(qǐng)求在 Next 被調(diào)用之前觸發(fā),這樣就能在 Next 被調(diào)用的時(shí)候,更早拿到結(jié)果返回,省掉了阻塞等待的過程。

在 copIterator 創(chuàng)建的時(shí)候,我們啟動(dòng)一個(gè)后臺(tái) worker goroutine 來依次執(zhí)行所有的 coprocessor task,并把執(zhí)行結(jié)果發(fā)送到一個(gè) response channel,這樣前臺(tái) Next 方法只需要從這個(gè) channel 里 ?receive 一個(gè) coprocessor response 就可以了。如果這個(gè) task 已經(jīng)執(zhí)行完成,Next 方法可以直接獲取到結(jié)果,立即返回。

當(dāng)所有 coprocessor task 被?work 執(zhí)行完成的時(shí)候,worker 把這個(gè) response channel 關(guān)閉,Next 方法在 receive channel 的時(shí)候發(fā)現(xiàn) channel 已經(jīng)關(guān)閉,就可以返回 nil response,表示所有結(jié)果都處理完成了。

以上的執(zhí)行方案還是存在一個(gè)問題,就是 coprocessor task 只有一個(gè) worker 在執(zhí)行,沒有并行,性能還是不理想。

為了增大并行度,我們可以構(gòu)造多個(gè) worker 來執(zhí)行 task,把所有的 task 發(fā)送到一個(gè) task channel,多個(gè) worker 從這一個(gè) channel 讀取 task,執(zhí)行完成后,把結(jié)果發(fā)到 response channel,通過設(shè)置 worker 的數(shù)量控制并發(fā)度。

這樣改造以后,就可以充分的并行執(zhí)行了,但是這樣帶來一個(gè)新的問題,task 是有序的,但是由于多個(gè) worker 并行執(zhí)行,返回的 response 順序是亂序的。對(duì)于不要求結(jié)果有序的 distsql 請(qǐng)求,這個(gè)執(zhí)行模式是可行的,我們使用這個(gè)模式來執(zhí)行。對(duì)于要求結(jié)果有序的 distsql 請(qǐng)求,就不能滿足要求了,我們需要另一種執(zhí)行模式。

當(dāng) worker 執(zhí)行完一個(gè) task 之后,當(dāng)前的做法是把 response 發(fā)送到一個(gè)全局的 channel 里,如果我們給每一個(gè) task 創(chuàng)建一個(gè) channel,把 response 發(fā)送到這個(gè) task 自己的 response channel 里,Next 的時(shí)候,就可以按照 task 的順序獲取 response,保證結(jié)果的有序。

以上就是 copIterator 最終的執(zhí)行模式。

copIterator 實(shí)現(xiàn)細(xì)節(jié)

理解執(zhí)行模式之后,我們從源碼的角度,分析一遍完整的執(zhí)行流程。

前臺(tái)執(zhí)行流程

前臺(tái)的執(zhí)行的第一步是 CopClient 的 Send 方法。先根據(jù) distsql 請(qǐng)求里的 KeyRanges 構(gòu)造 coprocessor task,用構(gòu)造好的 task 創(chuàng)建 copIterator,然后調(diào)用 copIterator 的 open 方法,啟動(dòng)多個(gè)后臺(tái) worker goroutine,然后啟動(dòng)一個(gè) sender 用來把 task 丟進(jìn) task channel,最后 copIterator 做為 kv.Reponse 返回。

前臺(tái)執(zhí)行的第二步是多次調(diào)用 kv.ResponseNext 方法,直到獲取所有的 response。

copIterator 在 Next 里會(huì)根據(jù)結(jié)果是否有序,選擇相應(yīng)的執(zhí)行模式,無序的請(qǐng)求會(huì)從 全局 channel 里獲取結(jié)果,有序的請(qǐng)求會(huì)在每一個(gè) task 的 response channel 里獲取結(jié)果。

后臺(tái)執(zhí)行流程

從 task channel 獲取到一個(gè) task 之后,worker 會(huì)執(zhí)行 handleTask 來發(fā)送 RPC 請(qǐng)求,并處理請(qǐng)求的異常,當(dāng) region 分裂的時(shí)候,我們需要重新構(gòu)造 新的 task,并重新發(fā)送。對(duì)于有序的 distsql 請(qǐng)求,分裂后的多個(gè) task 的執(zhí)行結(jié)果需要發(fā)送到舊的 task 的 response channel 里,所以一個(gè) task 的 response channel 可能會(huì)返回多個(gè) response,發(fā)送完成后需要 關(guān)閉 task 的 response channel。

twoPhaseCommitter 2PC 簡介

2PC 是實(shí)現(xiàn)分布式事務(wù)的一種方式,保證跨越多個(gè)網(wǎng)絡(luò)節(jié)點(diǎn)的事務(wù)的原子性,不會(huì)出現(xiàn)事務(wù)只提交一半的問題。

在 TiDB,使用的 2PC 模型是 Google percolator 模型,簡單的理解,percolator 模型和傳統(tǒng)的 2PC 的區(qū)別主要在于消除了事務(wù)管理器的單點(diǎn),把事務(wù)狀態(tài)信息保存在每個(gè) key 上,大幅提高了分布式事務(wù)的線性 scale 能力,雖然仍然存在一個(gè) timestamp oracle 的單點(diǎn),但是因?yàn)檫壿嫹浅:唵?,而且可?batch 執(zhí)行,所以并不會(huì)成為系統(tǒng)的瓶頸。

關(guān)于 percolator 模型的細(xì)節(jié),可以參考這篇文章的介紹 https://pingcap.com/blog-cn/percolator-and-txn/

構(gòu)造 twoPhaseCommitter

當(dāng)一個(gè)事務(wù)準(zhǔn)備提交的時(shí)候,會(huì)創(chuàng)建一個(gè) twoPhaseCommiter,用來執(zhí)行分布式的事務(wù)。

構(gòu)造的時(shí)候,需要做以下幾件事情

memBufferlockedKeys 里收集所有的 key 和 mutation

memBuffer 里的 key 是有序排列的,我們從頭遍歷 memBuffer 可以順序的收集到事務(wù)里需要修改的 key,value 長度為 0 的 entry 表示 DELETE 操作,value 長度大于 0 表示 PUT 操作,memBuffer 里的第一個(gè) key 做為事務(wù)的 primary key。lockKeys 里保存的是不需要修改,但需要加讀鎖的 key,也會(huì)做為 mutation 的 LOCK 操作,寫到 TiKV 上。

計(jì)算事務(wù)的大小是否超過限制

在收集 mutation 的時(shí)候,會(huì)統(tǒng)計(jì)整個(gè)事務(wù)的大小,如果超過了最大事務(wù)限制,會(huì)返回報(bào)錯(cuò)。

太大的事務(wù)可能會(huì)讓 TiKV 集群壓力過大,執(zhí)行失敗并導(dǎo)致集群不可用,所以要對(duì)事務(wù)的大小做出硬性的限制。

計(jì)算事務(wù)的 TTL 時(shí)間

如果一個(gè)事務(wù)的 key 通過 prewrite 加鎖后,事務(wù)沒有執(zhí)行完,tidb-server 就掛掉了,這時(shí)候集群內(nèi)其他 tidb-server 是無法讀取這個(gè) key 的,如果沒有 TTL,就會(huì)死鎖。設(shè)置了 TTL 之后,讀請(qǐng)求就可以在 TTL 超時(shí)之后執(zhí)行清鎖,然后讀取到數(shù)據(jù)。

我們計(jì)算一個(gè)事務(wù)的超時(shí)時(shí)間需要考慮正常執(zhí)行一個(gè)事務(wù)需要花費(fèi)的時(shí)間,如果太短會(huì)出現(xiàn)大的事務(wù)無法正常執(zhí)行完的問題,如果太長,會(huì)有異常退出導(dǎo)致某個(gè) key 長時(shí)間無法訪問的問題。所以使用了這樣一個(gè)算法,TTL 和事務(wù)的大小的平方根成正比,并控制在一個(gè)最小值和一個(gè)最大值之間。

execute

在 twoPhaseCommiter 創(chuàng)建好以后,下一步就是執(zhí)行 execute 函數(shù)。

execute 函數(shù)里,需要在 defer 函數(shù)里執(zhí)行 cleanupKeys,在事務(wù)沒有成功執(zhí)行的時(shí)候,清理掉多余的鎖,如果不做這一步操作,殘留的鎖會(huì)讓讀請(qǐng)求阻塞,直到 TTL 過期才會(huì)被清理。第一步會(huì)執(zhí)行 prewriteKeys,如果成功,會(huì)從 PD?獲取一個(gè) commitTS 用來執(zhí)行 commit 操作。取到了 commitTS 之后,還需要做以下驗(yàn)證:

commitTSstartTS

schema 沒有過期

事務(wù)的執(zhí)行時(shí)間沒有過長

如果沒有通過檢查,事務(wù)會(huì)失敗報(bào)錯(cuò)。

通過檢查之后,執(zhí)行最后一步 commitKeys,如果沒有錯(cuò)誤,事務(wù)就提交完成了。

當(dāng) commitKeys 請(qǐng)求遇到了網(wǎng)絡(luò)超時(shí),那么這個(gè)事務(wù)是否已經(jīng)提交是不確定的,這時(shí)候不能執(zhí)行 cleanupKeys 操作,否則就破壞了事務(wù)的一致性。我們對(duì)這種情況返回一個(gè)特殊的 undetermined error,讓上層來處理。上層會(huì)在遇到這種 error 的時(shí)候,把連接斷開,而不是返回給用一個(gè)執(zhí)行失敗的錯(cuò)誤。

prewriteKeys, ?commitKeys 和 cleanupKeys 有很多相同的邏輯,需要把 keys 根據(jù) region 分成 batch,然后對(duì)每個(gè) batch 執(zhí)行一次 RPC。

當(dāng) RPC?返回 region 過期的錯(cuò)誤時(shí),我們需要把這個(gè) region 上的 keys 重新分成 batch,發(fā)送 RPC 請(qǐng)求。

這部分邏輯我們把它抽出來,放在 doActionOnKeys 和 doActionOnBatches 里,并實(shí)現(xiàn) prewriteSinlgeBatch,commitSingleBatch,cleanupSingleBatch 函數(shù),用來執(zhí)行單個(gè) batch 的 RPC 請(qǐng)求。

雖大部分邏輯是相同的,但是不同的請(qǐng)求在執(zhí)行順序上有一些不同,在 doActionOnKeys 里需要特殊的判斷和處理。

prewrite 分成的多個(gè) batch 需要同步并行的執(zhí)行。

commit 分成的多個(gè) batch 需要先執(zhí)行第一個(gè) batch,成功后再異步并行執(zhí)行其他的 batch。

cleanup 分成的多個(gè) batch 需要異步并行執(zhí)行。

doActionOnBatches 會(huì)開啟多個(gè) goroutines 并行的執(zhí)行多個(gè) batch,如果遇到了 error,會(huì)把其他正在執(zhí)行的 context cancel 掉,然后返回第一個(gè)遇到的 error。

執(zhí)行 prewriteSingleBatch 的時(shí)候,有可能會(huì)遇到 region 分裂錯(cuò)誤,這時(shí)候 batch 里的 key 就不再是一個(gè) region 上的 key 了,我們會(huì)在這里遞歸的調(diào)用 prewriteKeys,重新走一遍拆分 batch 然后執(zhí)行 doActionOnBatchprewriteSingleBatch 的流程。這部分邏輯在 commitSingleBatchcleanupSingleBatch 里也都有。

twoPhaseCommitter 包含的邏輯只是事務(wù)模型的一小部分,主要的邏輯在 tikv-server 端,超出了這篇文章的范圍,就不在這里詳細(xì)討論了。

作者:周昱行

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/17797.html

相關(guān)文章

  • TiDB 源碼閱讀系列文章(十八)tikv-client(上)

    摘要:獲取所在的是通過向發(fā)送請(qǐng)求完成的。外部調(diào)用的接口,并不需要關(guān)心的細(xì)節(jié),請(qǐng)求都是為了實(shí)現(xiàn)接口而發(fā)起的。實(shí)現(xiàn)不同的接口需要發(fā)送不同的請(qǐng)求。這種錯(cuò)誤主要是因?yàn)榈姆至?,?dāng)內(nèi)的數(shù)據(jù)量增多以后,會(huì)分裂成多個(gè)新的。 作者:周昱行 在整個(gè) SQL 執(zhí)行過程中,需要經(jīng)過 Parser,Optimizer,Executor,DistSQL 這幾個(gè)主要的步驟,最終數(shù)據(jù)的讀寫是通過 tikv-client 與...

    zhjx922 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<