国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

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

whataa / 1084人閱讀

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

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

copIterator copIterator 是什么

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

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

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

所以它們的關(guān)系是這樣的,一個(gè) SQL 語(yǔ)句包含多個(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。接下來(lái),通過(guò) 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ì)超過(guò) region 的范圍。

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

copIterator 的執(zhí)行模式

為了更容易理解 copIterator 的執(zhí)行模式,我們先從最簡(jiǎn)單的實(shí)現(xiàn)方式開(kāi)始, 逐步推導(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,上層通過(guò)多次調(diào)用 Next 方法,獲取多個(gè) coprocessor response,直到所有結(jié)果獲取完。

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

這個(gè)執(zhí)行方式的一個(gè)很大的問(wèn)題,大量時(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é)果返回,省掉了阻塞等待的過(guò)程。

在 copIterator 創(chuàng)建的時(shí)候,我們啟動(dòng)一個(gè)后臺(tái) worker goroutine 來(lái)依次執(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è)問(wèn)題,就是 coprocessor task 只有一個(gè) worker 在執(zhí)行,沒(méi)有并行,性能還是不理想。

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

這樣改造以后,就可以充分的并行執(zhí)行了,但是這樣帶來(lái)一個(gè)新的問(wèn)題,task 是有序的,但是由于多個(gè) worker 并行執(zhí)行,返回的 response 順序是亂序的。對(duì)于不要求結(jié)果有序的 distsql 請(qǐng)求,這個(gè)執(zhí)行模式是可行的,我們使用這個(gè)模式來(lái)執(zhí)行。對(duì)于要求結(jié)果有序的 distsql 請(qǐng)求,就不能滿(mǎn)足要求了,我們需要另一種執(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 用來(lái)把 task 丟進(jìn) task channel,最后 copIterator 做為 kv.Reponse 返回。

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

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

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

從 task channel 獲取到一個(gè) task 之后,worker 會(huì)執(zhí)行 handleTask 來(lái)發(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 簡(jiǎn)介

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

在 TiDB,使用的 2PC 模型是 Google percolator 模型,簡(jiǎn)單的理解,percolator 模型和傳統(tǒng)的 2PC 的區(qū)別主要在于消除了事務(wù)管理器的單點(diǎn),把事務(wù)狀態(tài)信息保存在每個(gè) key 上,大幅提高了分布式事務(wù)的線性 scale 能力,雖然仍然存在一個(gè) timestamp oracle 的單點(diǎn),但是因?yàn)檫壿嫹浅:?jiǎ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,用來(lái)執(zhí)行分布式的事務(wù)。

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

memBufferlockedKeys 里收集所有的 key 和 mutation

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

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

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

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

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

如果一個(gè)事務(wù)的 key 通過(guò) prewrite 加鎖后,事務(wù)沒(méi)有執(zhí)行完,tidb-server 就掛掉了,這時(shí)候集群內(nèi)其他 tidb-server 是無(wú)法讀取這個(gè) key 的,如果沒(méi)有 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ù)無(wú)法正常執(zhí)行完的問(wèn)題,如果太長(zhǎng),會(huì)有異常退出導(dǎo)致某個(gè) key 長(zhǎng)時(shí)間無(wú)法訪問(wèn)的問(wèn)題。所以使用了這樣一個(gè)算法,TTL 和事務(wù)的大小的平方根成正比,并控制在一個(gè)最小值和一個(gè)最大值之間。

execute

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

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

commitTSstartTS

schema 沒(méi)有過(guò)期

事務(wù)的執(zhí)行時(shí)間沒(méi)有過(guò)長(zhǎng)

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

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

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

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

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

這部分邏輯我們把它抽出來(lái),放在 doActionOnKeys 和 doActionOnBatches 里,并實(shí)現(xiàn) prewriteSinlgeBatch,commitSingleBatch,cleanupSingleBatch 函數(shù),用來(lái)執(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ì)開(kāi)啟多個(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://m.specialneedsforspecialkids.com/yun/17797.html

相關(guān)文章

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

    摘要:獲取所在的是通過(guò)向發(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í)行過(guò)程中,需要經(jīng)過(guò) Parser,Optimizer,Executor,DistSQL 這幾個(gè)主要的步驟,最終數(shù)據(jù)的讀寫(xiě)是通過(guò) tikv-client 與...

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

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

0條評(píng)論

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