摘要:合庫(kù)合表數(shù)據(jù)同步在使用支撐大量數(shù)據(jù)時(shí),經(jīng)常會(huì)選擇使用分庫(kù)分表的方案。但當(dāng)將數(shù)據(jù)同步到后,通常希望邏輯上進(jìn)行合庫(kù)合表。為支持合庫(kù)合表的數(shù)據(jù)同步,主要實(shí)現(xiàn)了以下的一些功能。
作者:張學(xué)程
簡(jiǎn)介TiDB-DM(Data Migration)是用于將數(shù)據(jù)從 MySQL/MariaDB 遷移到 TiDB 的工具。該工具既支持以全量備份文件的方式將 MySQL/MariaDB 的數(shù)據(jù)導(dǎo)入到 TiDB,也支持通過(guò)解析執(zhí)行 MySQL/MariaDB binlog 的方式將數(shù)據(jù)增量同步到 TiDB。特別地,對(duì)于有多個(gè) MySQL/MariaDB 實(shí)例的分庫(kù)分表需要合并后同步到同一個(gè) TiDB 集群的場(chǎng)景,DM 提供了良好的支持。如果你需要從 MySQL/MariaDB 遷移到 TiDB,或者需要將 TiDB 作為 MySQL/MariaDB 的從庫(kù),DM 將是一個(gè)非常好的選擇。
架構(gòu)設(shè)計(jì)DM 是集群模式的,其主要由 DM-master、DM-worker 與 DM-ctl 三個(gè)組件組成,能夠以多對(duì)多的方式將多個(gè)上游 MySQL 實(shí)例的數(shù)據(jù)同步到多個(gè)下游 TiDB 集群,其架構(gòu)圖如下:
DM-master:管理整個(gè) DM 集群,維護(hù)集群的拓?fù)湫畔ⅲO(jiān)控各個(gè) DM-worker 實(shí)例的運(yùn)行狀態(tài);進(jìn)行數(shù)據(jù)同步任務(wù)的拆解與分發(fā),監(jiān)控?cái)?shù)據(jù)同步任務(wù)的執(zhí)行狀態(tài);在進(jìn)行合庫(kù)合表的增量數(shù)據(jù)同步時(shí),協(xié)調(diào)各 DM-worker 上 DDL 的執(zhí)行或跳過(guò);提供數(shù)據(jù)同步任務(wù)管理的統(tǒng)一入口。
DM-worker:與上游 MySQL 實(shí)例一一對(duì)應(yīng),執(zhí)行具體的全量、增量數(shù)據(jù)同步任務(wù);將上游 MySQL 的 binlog 拉取到本地并持久化保存;根據(jù)定義的數(shù)據(jù)同步任務(wù),將上游 MySQL 數(shù)據(jù)全量導(dǎo)出成 SQL 文件后導(dǎo)入到下游 TiDB,或解析本地持久化的 binlog 后增量同步到下游 TiDB;編排 DM-master 拆解后的數(shù)據(jù)同步子任務(wù),監(jiān)控子任務(wù)的運(yùn)行狀態(tài)。
DM-ctl:命令行交互工具,通過(guò)連接到 DM-master 后,執(zhí)行 DM 集群的管理與數(shù)據(jù)同步任務(wù)的管理。
實(shí)現(xiàn)原理 數(shù)據(jù)遷移流程單個(gè) DM 集群可以同時(shí)運(yùn)行多個(gè)數(shù)據(jù)同步任務(wù);對(duì)于每一個(gè)同步任務(wù),可以拆解為多個(gè)子任務(wù)同時(shí)由多個(gè) DM-worker 節(jié)點(diǎn)承擔(dān),其中每個(gè) DM-worker 節(jié)點(diǎn)負(fù)責(zé)同步來(lái)自對(duì)應(yīng)的上游 MySQL 實(shí)例的數(shù)據(jù)。對(duì)于單個(gè) DM-worker 節(jié)點(diǎn)上的單個(gè)數(shù)據(jù)同步子任務(wù),其數(shù)據(jù)遷移流程如下,其中上部的數(shù)據(jù)流向?yàn)槿繑?shù)據(jù)遷移、下部的數(shù)據(jù)流向?yàn)樵隽繑?shù)據(jù)同步:
在每個(gè) DM-worker 節(jié)點(diǎn)內(nèi)部,對(duì)于特定的數(shù)據(jù)同步子任務(wù),主要由 dumper、loader、relay 與 syncer(binlog replication)等數(shù)據(jù)同步處理單元執(zhí)行具體的數(shù)據(jù)同步操作。
對(duì)于全量數(shù)據(jù)遷移,DM 首先使用 dumper 單元從上游 MySQL 中將表結(jié)構(gòu)與數(shù)據(jù)導(dǎo)出成 SQL 文件;然后使用 loader 單元讀取這些 SQL 文件并同步到下游 TiDB。
對(duì)于增量數(shù)據(jù)同步,首先使用 relay 單元作為 slave 連接到上游 MySQL 并拉取 binlog 數(shù)據(jù)后作為 relay log 持久化存儲(chǔ)在本地,然后使用 syncer 單元讀取這些 relay log 并解析構(gòu)造成 SQL 語(yǔ)句后同步到下游 TiDB。這個(gè)增量同步的過(guò)程與 MySQL 的主從復(fù)制類(lèi)似,主要區(qū)別在于在 DM 中,本地持久化的 relay log 可以同時(shí)供多個(gè)不同子任務(wù)的 syncer 單元所共用,避免了多個(gè)任務(wù)需要重復(fù)從上游 MySQL 拉取 binlog 的問(wèn)題。
數(shù)據(jù)遷移并發(fā)模型為加快數(shù)據(jù)導(dǎo)入速度,在 DM 中不論是全量數(shù)據(jù)遷移,還是增量數(shù)據(jù)同步,都在其中部分階段使用了并發(fā)處理。
對(duì)于全量數(shù)據(jù)遷移,在導(dǎo)出階段,dumper 單元調(diào)用 mydumper 導(dǎo)出工具執(zhí)行實(shí)際的數(shù)據(jù)導(dǎo)出操作,對(duì)應(yīng)的并發(fā)模型可以直接參考 mydumper 的源碼。在使用 loader 單元執(zhí)行的導(dǎo)入階段,對(duì)應(yīng)的并發(fā)模型結(jié)構(gòu)如下:
使用 mydumper 執(zhí)行導(dǎo)出時(shí),可以通過(guò) --chunk-filesize 等參數(shù)將單個(gè)表拆分成多個(gè) SQL 文件,這些 SQL 文件對(duì)應(yīng)的都是上游 MySQL 某一個(gè)時(shí)刻的靜態(tài)快照數(shù)據(jù),且各 SQL 文件間的數(shù)據(jù)不存在關(guān)聯(lián)。因此,在使用 loader 單元執(zhí)行導(dǎo)入時(shí),可以直接在一個(gè) loader 單元內(nèi)啟動(dòng)多個(gè) worker 工作協(xié)程,由各 worker 協(xié)程并發(fā)、獨(dú)立地每次讀取一個(gè)待導(dǎo)入的 SQL 文件進(jìn)行導(dǎo)入。即 loader 導(dǎo)入階段,是以 SQL 文件級(jí)別粒度并發(fā)進(jìn)行的。在 DM 的任務(wù)配置中,對(duì)于 loader 單元,其中的 pool-size 參數(shù)即用于控制此處 worker 協(xié)程數(shù)量。
對(duì)于增量數(shù)據(jù)同步,在從上游拉取 binlog 并持久化到本地的階段,由于上游 MySQL 上 binlog 的產(chǎn)生與發(fā)送是以 stream 形式進(jìn)行的,因此這部分只能串行處理。在使用 syncer 單元執(zhí)行的導(dǎo)入階段,在一定的限制條件下,可以執(zhí)行并發(fā)導(dǎo)入,對(duì)應(yīng)的模型結(jié)構(gòu)如下:
當(dāng) syncer 讀取與解析本地 relay log 時(shí),與從上游拉取 binlog 類(lèi)似,是以 stream 形式進(jìn)行的,因此也只能串行處理。當(dāng) syncer 解析出各 binlog event 并構(gòu)造成待同步的 job 后,則可以根據(jù)對(duì)應(yīng)行數(shù)據(jù)的主鍵、索引等信息經(jīng)過(guò) hash 計(jì)算后分發(fā)到多個(gè)不同的待同步 job channel 中;在 channel 的另一端,與各個(gè) channel 對(duì)應(yīng)的 worker 協(xié)程并發(fā)地從 channel 中取出 job 后同步到下游的 TiDB。即 syncer 導(dǎo)入階段,是以 binlog event 級(jí)別粒度并發(fā)進(jìn)行的。在 DM 的任務(wù)配置中,對(duì)于 syncer 單元,其中的 worker-count 參數(shù)即用于控制此處 worker 協(xié)程數(shù)量。
但 syncer 并發(fā)同步到下游 TiDB 時(shí),存在一些限制,主要包括:
對(duì)于 DDL,由于會(huì)變更下游的表結(jié)構(gòu),因此必須確保在舊表結(jié)構(gòu)對(duì)應(yīng)的 DML 都同步完成后,才能進(jìn)行同步。在 DM 中,當(dāng)解析 binlog event 得到 DDL 后,會(huì)向每一個(gè) job channel 發(fā)送一個(gè)特殊的 flush job;當(dāng)各 worker 協(xié)程遇到 flush job 時(shí),會(huì)立刻向下游 TiDB 同步之前已經(jīng)取出的所有 job;等各 job channel 中的 job 都同步到下游 TiDB 后,開(kāi)始同步 DDL;等待 DDL 同步完成后,繼續(xù)同步后續(xù)的 DML。即 DDL 不能與 DML 并發(fā)同步,且 DDL 之前與之后的 DML 也不能并發(fā)同步。sharding 場(chǎng)景下 DDL 的同步處理見(jiàn)后文。
對(duì)于 DML,多條 DML 可能會(huì)修改同一行的數(shù)據(jù),甚至是主鍵。如果并發(fā)地同步這些 DML,則可能造成同步后數(shù)據(jù)的不一致。DM 中對(duì)于 DML 之間的沖突檢測(cè)與處理,與 TiDB-Binlog 中的處理類(lèi)似,具體原理可以閱讀《TiDB EcoSystem Tools 原理解讀(一)TiDB-Binlog 架構(gòu)演進(jìn)與實(shí)現(xiàn)原理》中關(guān)于 Drainer 內(nèi) SQL 之間沖突檢測(cè)的討論。
合庫(kù)合表數(shù)據(jù)同步在使用 MySQL 支撐大量數(shù)據(jù)時(shí),經(jīng)常會(huì)選擇使用分庫(kù)分表的方案。但當(dāng)將數(shù)據(jù)同步到 TiDB 后,通常希望邏輯上進(jìn)行合庫(kù)合表。DM 為支持合庫(kù)合表的數(shù)據(jù)同步,主要實(shí)現(xiàn)了以下的一些功能。
table router為說(shuō)明 DM 中 table router(表名路由)功能,先看如下圖所示的一個(gè)例子:
在這個(gè)例子中,上游有 2 個(gè) MySQL 實(shí)例,每個(gè)實(shí)例有 2 個(gè)邏輯庫(kù),每個(gè)庫(kù)有 2 個(gè)表,總共 8 個(gè)表。當(dāng)同步到下游 TiDB 后,希望所有的這 8 個(gè)表最終都合并同步到同一個(gè)表中。
但為了能將 8 個(gè)來(lái)自不同實(shí)例、不同庫(kù)且有不同名的表同步到同一個(gè)表中,首先要處理的,就是要能根據(jù)某些定義好的規(guī)則,將來(lái)自不同表的數(shù)據(jù)都路由到下游的同一個(gè)表中。在 DM 中,這類(lèi)規(guī)則叫做 router-rules。對(duì)于上面的示例,其規(guī)則如下:
name-of-router-rule: schema-pattern: "schema_*" table-pattern: "table_*" target-schema: "schema" target-table: "table"
name-of-router-rule:規(guī)則名,用戶(hù)指定。當(dāng)有多個(gè)上游實(shí)例需要使用相同的規(guī)則時(shí),可以只定義一條規(guī)則,多個(gè)不同的實(shí)例通過(guò)規(guī)則名進(jìn)行引用。
schema-pattern:用于匹配上游庫(kù)(schema)名的模式,支持在尾部使用通配符(*)。這里使用 schema_* 即可匹配到示例中的兩個(gè)庫(kù)名。
table-pattern:用于匹配上游表名的模式,與 schema-pattern 類(lèi)似。這里使用 table_* 即可匹配到示例中的兩個(gè)表名。
target-schema:目標(biāo)庫(kù)名。對(duì)于庫(kù)名、表名匹配的數(shù)據(jù),將被路由到這個(gè)庫(kù)中。
target-table:目標(biāo)表名。對(duì)于庫(kù)名、表名匹配的數(shù)據(jù),將被路由到 target-schema 庫(kù)下的這個(gè)表中。
在 DM 內(nèi)部實(shí)現(xiàn)上,首先根據(jù) schema-pattern / table-pattern 構(gòu)造對(duì)應(yīng)的 trie 結(jié)構(gòu),并將規(guī)則存儲(chǔ)在 trie 節(jié)點(diǎn)中;當(dāng)有 SQL 需要同步到下游時(shí),通過(guò)使用上游庫(kù)名、表名查詢(xún) trie 即可得到對(duì)應(yīng)的規(guī)則,并根據(jù)規(guī)則替換原 SQL 中的庫(kù)名、表名;通過(guò)向下游 TiDB 執(zhí)行替換后的 SQL 即完成了根據(jù)表名的路由同步。有關(guān) router-rules 規(guī)則的具體實(shí)現(xiàn),可以閱讀 TiDB-Tools 下的 table-router pkg 源代碼。
column mapping有了 table router 功能,已經(jīng)可以完成基本的合庫(kù)合表數(shù)據(jù)同步了。但在數(shù)據(jù)庫(kù)中,我們經(jīng)常會(huì)使用自增類(lèi)型的列作為主鍵。如果多個(gè)上游分表的主鍵各自獨(dú)立地自增,將它們合并同步到下游后,就很可能會(huì)出現(xiàn)主鍵沖突,造成數(shù)據(jù)的不一致。我們可看一個(gè)如下的例子:
在這個(gè)例子中,上游 4 個(gè)需要合并同步到下游的表中,都存在 id 列值為 1 的記錄。假設(shè)這個(gè) id 列是表的主鍵。在同步到下游的過(guò)程中,由于相關(guān)更新操作是以 id 列作為條件來(lái)確定需要更新的記錄,因此會(huì)造成后同步的數(shù)據(jù)覆蓋前面已經(jīng)同步過(guò)的數(shù)據(jù),導(dǎo)致部分?jǐn)?shù)據(jù)的丟失。
在 DM 中,我們通過(guò) column mapping 功能在數(shù)據(jù)同步的過(guò)程中依據(jù)指定規(guī)則對(duì)相關(guān)列的數(shù)據(jù)進(jìn)行轉(zhuǎn)換改寫(xiě)來(lái)避免數(shù)據(jù)沖突與丟失。對(duì)于上面的示例,其中 MySQL 實(shí)例 1 的 column mapping 規(guī)則如下:
mapping-rule-of-instance-1: schema-pattern: "schema_*" table-pattern: "table_*" expression: "partition id" source-column: "id" target-column: "id" arguments: ["1", "schema_", "table_"]
mapping-rule-of-instance-1:規(guī)則名,用戶(hù)指定。由于不同的上游 MySQL 實(shí)例需要轉(zhuǎn)換得到不同的值,因此通常每個(gè) MySQL 實(shí)例使用一條專(zhuān)有的規(guī)則。
schema-pattern / table-pattern:上游庫(kù)名、表名匹配模式,與 router-rules 中的對(duì)應(yīng)配置項(xiàng)一致。
expression:進(jìn)行數(shù)據(jù)轉(zhuǎn)換的表達(dá)式名。目前常用的表達(dá)式即為 "partition id",有關(guān)該表達(dá)式的具體說(shuō)明見(jiàn)下文。
source-column:轉(zhuǎn)換表達(dá)式的輸入數(shù)據(jù)對(duì)應(yīng)的來(lái)源列名,"id" 表示這個(gè)表達(dá)式將作用于表中名為 id 的列。暫時(shí)只支持對(duì)單個(gè)來(lái)源列進(jìn)行數(shù)據(jù)轉(zhuǎn)換。
target-column:轉(zhuǎn)換表達(dá)式的輸出數(shù)據(jù)對(duì)應(yīng)的目標(biāo)列名,與 source-column 類(lèi)似。暫時(shí)只支持對(duì)單個(gè)目標(biāo)列進(jìn)行數(shù)據(jù)轉(zhuǎn)換,且對(duì)應(yīng)的目標(biāo)列必須已經(jīng)存在。
arguments:轉(zhuǎn)換表達(dá)式所依賴(lài)的參數(shù)。參數(shù)個(gè)數(shù)與含義依具體表達(dá)式而定。
partition id 是目前主要受支持的轉(zhuǎn)換表達(dá)式,其通過(guò)為 bigint 類(lèi)型的值增加二進(jìn)制前綴來(lái)解決來(lái)自不同表的數(shù)據(jù)合并同步后可能產(chǎn)生沖突的問(wèn)題。partition id 的 arguments 包括 3 個(gè)參數(shù),分別為:
MySQL 實(shí)例 ID:標(biāo)識(shí)數(shù)據(jù)的來(lái)源 MySQL 實(shí)例,用戶(hù)自由指定。如 "1" 表示匹配該規(guī)則的數(shù)據(jù)來(lái)自于 MySQL 實(shí)例 1,且這個(gè)標(biāo)識(shí)將被轉(zhuǎn)換成數(shù)值后以二進(jìn)制的形式作為前綴的一部分添加到轉(zhuǎn)換后的值中。
庫(kù)名前綴:標(biāo)識(shí)數(shù)據(jù)的來(lái)源邏輯庫(kù)。如 "schema_" 應(yīng)用于 schema_2 邏輯庫(kù)時(shí),表示去除前綴后剩下的部分(數(shù)字 2)將以二進(jìn)制的形式作為前綴的一部分添加到轉(zhuǎn)換后的值中。
表名前綴:標(biāo)識(shí)數(shù)據(jù)的來(lái)源表。如 "table_" 應(yīng)用于 table_3 表時(shí),表示去除前綴后剩下的部分(數(shù)字 3)將以二進(jìn)制的形式作為前綴的一部分添加到轉(zhuǎn)換后的值中。
各部分在經(jīng)過(guò)轉(zhuǎn)換后的數(shù)值中的二進(jìn)制分布如下圖所示(各部分默認(rèn)所占用的 bits 位數(shù)如圖所示):
假如轉(zhuǎn)換前的原始數(shù)據(jù)為 123,且有如上的 arguments 參數(shù)設(shè)置,則轉(zhuǎn)換后的值為:
1<<(64-1-4) | 2<<(64-1-4-7) | 3<<(64-1-4-7-8) | 123
另外,arguments 中的 3 個(gè)參數(shù)均可設(shè)置為空字符串(""),即表示該部分不被添加到轉(zhuǎn)換后的值中,且不占用額外的 bits。比如將其設(shè)置為["1", "", "table_"],則轉(zhuǎn)換后的值為:
1 << (64-1-4) | 3<< (64-1-4-8) | 123
有關(guān) column mapping 功能的具體實(shí)現(xiàn),可以閱讀 TiDB-Tools 下的 column-mapping pkg 源代碼。
sharding DDL有了 table router 和 column mapping 功能,DML 的合庫(kù)合表數(shù)據(jù)同步已經(jīng)可以正常進(jìn)行了。但如果在增量數(shù)據(jù)同步的過(guò)程中,上游待合并的分表上執(zhí)行了 DDL 操作,則可能出現(xiàn)問(wèn)題。我們先來(lái)看一個(gè)簡(jiǎn)化后的在分表上執(zhí)行 DDL 的例子。
在上圖的例子中,分表的合庫(kù)合表簡(jiǎn)化成了上游只有兩個(gè) MySQL 實(shí)例,每個(gè)實(shí)例內(nèi)只有一個(gè)表。假設(shè)在開(kāi)始數(shù)據(jù)同步時(shí),將兩個(gè)分表的表結(jié)構(gòu) schema 的版本記為 schema V1,將 DDL 執(zhí)行完成后的表結(jié)構(gòu) schema 的版本記為 schema V2。
現(xiàn)在,假設(shè)數(shù)據(jù)同步過(guò)程中,從兩個(gè)上游分表收到的 binlog 數(shù)據(jù)有如下的時(shí)序:
開(kāi)始同步時(shí),從兩個(gè)分表收到的都是 schema V1 的 DML。
在 t1 時(shí)刻,收到實(shí)例 1 上分表的 DDL。
從 t2 時(shí)刻開(kāi)始,從實(shí)例 1 收到的是 schema V2 的 DML;但從實(shí)例 2 收到的仍是 schema V1 的 DML。
在 t3 時(shí)刻,收到實(shí)例 2 上分表的 DDL。
從 t4 時(shí)刻開(kāi)始,從實(shí)例 2 收到的也是 schema V2 的 DML。
假設(shè)在數(shù)據(jù)同步過(guò)程中,不對(duì)分表的 DDL 進(jìn)行處理。當(dāng)將實(shí)例 1 的 DDL 同步到下游后,下游的表結(jié)構(gòu)會(huì)變更成為 schema V2。但對(duì)于實(shí)例 2,在 t2 時(shí)刻到 t3 時(shí)刻這段時(shí)間內(nèi)收到的仍然是 schema V1 的 DML。當(dāng)嘗試把這些與 schema V1 對(duì)應(yīng)的 DML 同步到下游時(shí),就會(huì)由于 DML 與表結(jié)構(gòu)的不一致而發(fā)生錯(cuò)誤,造成數(shù)據(jù)無(wú)法正確同步。
繼續(xù)使用上面的例子,來(lái)看看我們?cè)?DM 中是如何處理合庫(kù)合表過(guò)程中的 DDL 同步的。
在這個(gè)例子中,DM-worker-1 用于同步來(lái)自 MySQL 實(shí)例 1 的數(shù)據(jù),DM-worker-2 用于同步來(lái)自 MySQL 實(shí)例 2 的數(shù)據(jù),DM-master 用于協(xié)調(diào)多個(gè) DM-worker 間的 DDL 同步。從 DM-worker-1 收到 DDL 開(kāi)始,簡(jiǎn)化后的 DDL 同步流程為:
DM-worker-1 在 t1 時(shí)刻收到來(lái)自 MySQL 實(shí)例 1 的 DDL,自身暫停該 DDL 對(duì)應(yīng)任務(wù)的 DDL 及 DML 數(shù)據(jù)同步,并將 DDL 相關(guān)信息發(fā)送給 DM-master。
DM-master 根據(jù) DDL 信息判斷需要協(xié)調(diào)該 DDL 的同步,為該 DDL 創(chuàng)建一個(gè)鎖,并將 DDL 鎖信息發(fā)回給 DM-worker-1,同時(shí)將 DM-worker-1 標(biāo)記為這個(gè)鎖的 owner。
DM-worker-2 繼續(xù)進(jìn)行 DML 的同步,直到在 t3 時(shí)刻收到來(lái)自 MySQL 實(shí)例 2 的 DDL,自身暫停該 DDL 對(duì)應(yīng)任務(wù)的數(shù)據(jù)同步,并將 DDL 相關(guān)信息發(fā)送給 DM-master。
DM-master 根據(jù) DDL 信息判斷該 DDL 對(duì)應(yīng)的鎖信息已經(jīng)存在,直接將對(duì)應(yīng)鎖信息發(fā)回給 DM-worker-2。
DM-master 根據(jù)啟動(dòng)任務(wù)時(shí)的配置信息、上游 MySQL 實(shí)例分表信息、部署拓?fù)湫畔⒌龋袛嗟弥呀?jīng)收到了需要合表的所有上游分表的該 DDL,請(qǐng)求 DDL 鎖的 owner(DM-worker-1)向下游同步執(zhí)行該 DDL。
DM-worker-1 根據(jù) step 2 時(shí)收到的 DDL 鎖信息驗(yàn)證 DDL 執(zhí)行請(qǐng)求;向下游執(zhí)行 DDL,并將執(zhí)行結(jié)果反饋給 DM-master;若執(zhí)行 DDL 成功,則自身開(kāi)始繼續(xù)同步后續(xù)的(從 t2 時(shí)刻對(duì)應(yīng)的 binlog 開(kāi)始的)DML。
DM-master 收到來(lái)自 owner 執(zhí)行 DDL 成功的響應(yīng),請(qǐng)求在等待該 DDL 鎖的所有其他 DM-worker(DM-worker-2)忽略該 DDL,直接繼續(xù)同步后續(xù)的(從 t4 時(shí)刻對(duì)應(yīng)的 binlog 開(kāi)始的)DML。
根據(jù)上面 DM 處理多個(gè) DM-worker 間的 DDL 同步的流程,歸納一下 DM 內(nèi)處理多個(gè) DM-worker 間 sharding DDL 同步的特點(diǎn):
根據(jù)任務(wù)配置與 DM 集群部署拓?fù)湫畔ⅲ?DM-master 內(nèi)建立一個(gè)需要協(xié)調(diào) DDL 同步的邏輯 sharding group,group 中的成員為處理該任務(wù)拆解后各子任務(wù)的 DM-worker。
各 DM-worker 在從 binlog event 中獲取到 DDL 后,會(huì)將 DDL 信息發(fā)送給 DM-master。
DM-master 根據(jù)來(lái)自 DM-worker 的 DDL 信息及 sharding group 信息創(chuàng)建/更新 DDL 鎖。
如果 sharding group 的所有成員都收到了某一條 DDL,則表明上游分表在該 DDL 執(zhí)行前的 DML 都已經(jīng)同步完成,可以執(zhí)行 DDL,并繼續(xù)后續(xù)的 DML 同步。
上游分表的 DDL 在經(jīng)過(guò) table router 轉(zhuǎn)換后,對(duì)應(yīng)需要在下游執(zhí)行的 DDL 應(yīng)該一致,因此僅需 DDL 鎖的 owner 執(zhí)行一次即可,其他 DM-worker 可直接忽略對(duì)應(yīng)的 DDL。
從 DM 處理 DM-worker 間 sharding DDL 同步的特點(diǎn),可以看出該功能存在以下一些限制:
上游的分表必須以相同的順序執(zhí)行(table router 轉(zhuǎn)換后相同的)DDL,比如表 1 先增加列 a 后再增加列 b,而表 2 先增加列 b 后再增加列 a,這種不同順序的 DDL 執(zhí)行方式是不支持的。
一個(gè)邏輯 sharding group 內(nèi)的所有 DM-worker 對(duì)應(yīng)的上游分表,都應(yīng)該執(zhí)行對(duì)應(yīng)的 DDL,比如其中有 DM-worker-2 對(duì)應(yīng)的上游分表未執(zhí)行 DDL,則其他已執(zhí)行 DDL 的 DM-worker 都會(huì)暫停同步任務(wù),等待 DM-worker-2 收到對(duì)應(yīng)上游的 DDL。
由于已經(jīng)收到的 DDL 的 DM-worker 會(huì)暫停任務(wù)以等待其他 DM-worker 收到對(duì)應(yīng)的 DDL,因此數(shù)據(jù)同步延遲會(huì)增加。
增量同步開(kāi)始時(shí),需要合并的所有上游分表結(jié)構(gòu)必須一致,才能確保來(lái)自不同分表的 DML 可以同步到一個(gè)確定表結(jié)構(gòu)的下游,也才能確保后續(xù)各分表的 DDL 能夠正確匹配與同步。
在上面的示例中,每個(gè) DM-worker 對(duì)應(yīng)的上游 MySQL 實(shí)例中只有一個(gè)需要進(jìn)行合并的分表。但在實(shí)際場(chǎng)景下,一個(gè) MySQL 實(shí)例可能有多個(gè)分庫(kù)內(nèi)的多個(gè)分表需要進(jìn)行合并,比如前面介紹 table router 與 column mapping 功能時(shí)的例子。當(dāng)一個(gè) MySQL 實(shí)例中有多個(gè)分表需要合并時(shí),sharding DDL 的協(xié)調(diào)同步過(guò)程增加了更多的復(fù)雜性。
假設(shè)同一個(gè) MySQL 實(shí)例中有 table_1 和 table_2 兩個(gè)分表需要進(jìn)行合并,如下圖:
由于數(shù)據(jù)來(lái)自同一個(gè) MySQL 實(shí)例,因此所有數(shù)據(jù)都是從同一個(gè) binlog 流中獲得。在這個(gè)例子中,時(shí)序如下:
開(kāi)始同步時(shí),兩個(gè)分表收到的數(shù)據(jù)都是 schema V1 的 DML。
在 t1 時(shí)刻,收到了 table_1 的 DDL。
從 t2 時(shí)刻到 t3 時(shí)刻,收到的數(shù)據(jù)同時(shí)包含 table_1 schema V2 的 DML 及 table_2 schema V1 的 DML。
在 t3 時(shí)刻,收到了 table_2 的 DDL。
從 t4 時(shí)刻開(kāi)始,兩個(gè)分表收到的數(shù)據(jù)都是 schema V2 的 DML。
假設(shè)在數(shù)據(jù)同步過(guò)程中不對(duì) DDL 進(jìn)行特殊處理,當(dāng) table_1 的 DDL 同步到下游、變更下游表結(jié)構(gòu)后,table_2 schema V1 的 DML 將無(wú)法正常同步。因此,在單個(gè) DM-worker 內(nèi)部,我們也構(gòu)造了與 DM-master 內(nèi)類(lèi)似的邏輯 sharding group,但 group 的成員是同一個(gè)上游 MySQL 實(shí)例的不同分表。
但 DM-worker 內(nèi)協(xié)調(diào)處理 sharding group 的同步不能完全與 DM-master 處理時(shí)一致,主要原因包括:
當(dāng)收到 table_1 的 DDL 時(shí),同步不能暫停,需要繼續(xù)解析 binlog 才能獲得后續(xù) table_2 的 DDL,即需要從 t2 時(shí)刻繼續(xù)向前解析直到 t3 時(shí)刻。
在繼續(xù)解析 t2 時(shí)刻到 t3 時(shí)刻的 binlog 的過(guò)程中,table_1 的 schema V2 的 DML 不能向下游同步;但在 sharding DDL 同步并執(zhí)行成功后,這些 DML 需要同步到下游。
在 DM 中,簡(jiǎn)化后的 DM-worker 內(nèi) sharding DDL 同步流程為:
在 t1 時(shí)刻收到 table_1 的 DDL,記錄 DDL 信息及此時(shí)的 binlog 位置點(diǎn)信息。
繼續(xù)向前解析 t2 時(shí)刻到 t3 時(shí)刻的 binlog。
對(duì)于屬于 table_1 的 schema V2 DML,忽略;對(duì)于屬于 table_2 的 schema V1 DML,正常同步到下游。
在 t3 時(shí)刻收到 table_2 的 DDL,記錄 DDL 信息及此時(shí)的 binlog 位置點(diǎn)信息。
根據(jù)同步任務(wù)配置信息、上游庫(kù)表信息等,判斷該 MySQL 實(shí)例上所有分表的 DDL 都已經(jīng)收到;將 DDL 同步到下游執(zhí)行、變更下游表結(jié)構(gòu)。
設(shè)置新的 binlog 流的解析起始位置點(diǎn)為 step 1 時(shí)保存的位置點(diǎn)。
重新開(kāi)始解析從 t2 時(shí)刻到 t3 時(shí)刻的 binlog。
對(duì)于屬于 table_1 的 schema V2 DML,正常同步到下游;對(duì)于屬于 table_2 的 shema V1 DML,忽略。
解析到達(dá) step 4 時(shí)保存的 binlog 位置點(diǎn),可得知在 step 3 時(shí)被忽略的所有 DML 都已經(jīng)重新同步到下游。
繼續(xù)從 t4 時(shí)刻對(duì)應(yīng)的 binlog 位置點(diǎn)正常同步。
從上面的分析可以知道,DM 在處理 sharding DDL 同步時(shí),主要通過(guò)兩級(jí) sharding group 來(lái)進(jìn)行協(xié)調(diào)控制,簡(jiǎn)化的流程為:
各 DM-worker 獨(dú)立地協(xié)調(diào)對(duì)應(yīng)上游 MySQL 實(shí)例內(nèi)多個(gè)分表組成的 sharding group 的 DDL 同步。
當(dāng) DM-worker 內(nèi)所有分表的 DDL 都收到時(shí),向 DM-master 發(fā)送 DDL 相關(guān)信息。
DM-master 根據(jù) DM-worker 發(fā)來(lái)的 DDL 信息,協(xié)調(diào)由各 DM-worker 組成的 sharing group 的 DDL 同步。
當(dāng) DM-master 收到所有 DM-worker 的 DDL 信息時(shí),請(qǐng)求 DDL lock 的 owner(某個(gè) DM-worker)執(zhí)行 DDL。
owner 執(zhí)行 DDL,并將結(jié)果反饋給 DM-master;自身開(kāi)始重新同步在內(nèi)部協(xié)調(diào) DDL 同步過(guò)程中被忽略的 DML。
當(dāng) DM-master 發(fā)現(xiàn) owner 執(zhí)行 DDL 成功后,請(qǐng)求其他所有 DM-worker 開(kāi)始繼續(xù)同步。
其他所有 DM-worker 各自開(kāi)始重新同步在內(nèi)部協(xié)調(diào) DDL 同步過(guò)程中被忽略的 DML。
所有 DM-worker 在重新同步完成被忽略的 DML 后,繼續(xù)正常同步。
數(shù)據(jù)同步過(guò)濾在進(jìn)行數(shù)據(jù)同步的過(guò)程中,有時(shí)可能并不需要將上游所有的數(shù)據(jù)都同步到下游,這時(shí)一般期望能在同步過(guò)程中根據(jù)某些規(guī)則,過(guò)濾掉部分不期望同步的數(shù)據(jù)。在 DM 中,支持 2 種不同級(jí)別的同步過(guò)濾方式。
庫(kù)表黑白名單DM 在 dumper、loader、syncer 三個(gè)處理單元中都支持配置規(guī)則只同步/不同步部分庫(kù)或表。
對(duì)于 dumper 單元,其實(shí)際調(diào)用 mydumper 來(lái) dump 上游 MySQL 的數(shù)據(jù)。比如只期望導(dǎo)出 test 庫(kù)中的 t1、t2 兩個(gè)表的數(shù)據(jù),則可以為 dumper 單元配置如下規(guī)則:
name-of-dump-rule: extra-args: "-B test -T t1,t2"
name-of-dump-rule:規(guī)則名,用戶(hù)指定。當(dāng)有多個(gè)上游實(shí)例需要使用相同的規(guī)則時(shí),可以只定義一條規(guī)則,多個(gè)不同的實(shí)例通過(guò)規(guī)則名進(jìn)行引用。
extra-args:dumper 單元額外參數(shù)。除 dumper 單元中明確定義的配置項(xiàng)外的其他所有 mydumper 配置項(xiàng)都通過(guò)此參數(shù)傳入,格式與使用 mydumper 時(shí)一致。
有關(guān) mydumper 對(duì)庫(kù)表黑白名單的支持,可查看 mydumper 的參數(shù)及 mydumper 的源碼。
對(duì)于 loader 和 syncer 單元,其對(duì)應(yīng)的庫(kù)表黑白名單規(guī)則為 black-white-list。假設(shè)只期望同步 test 庫(kù)中的 t1、t2 兩個(gè)表的數(shù)據(jù),則可配置如下規(guī)則:
name-of-bwl-rule: do-tables: - db-name: "test" tbl-name: "t1" - db-name: "test" tbl-name: "t2"
示例中只使用了該規(guī)則的部分配置項(xiàng),完整的配置項(xiàng)及各配置項(xiàng)的含義,可閱讀該功能對(duì)應(yīng)的用戶(hù)文檔。DM 中該規(guī)則與 MySQL 的主從同步過(guò)濾規(guī)則類(lèi)似,因此也可參考 Evaluation of Database-Level Replication and Binary Logging Options 與 Evaluation of Table-Level Replication Options。
對(duì)于 loader 單元,在解析 SQL 文件名獲得庫(kù)名表名后,會(huì)與配置的黑白名單規(guī)則進(jìn)行匹配,如果匹配結(jié)果為不需要同步,則會(huì)忽略對(duì)應(yīng)的整個(gè) SQL 文件。對(duì)于 syncer 單元,在解析 binlog 獲得庫(kù)名表名后,會(huì)與配置的黑白名單規(guī)則進(jìn)行匹配,如果匹配結(jié)果為不需要同步,則會(huì)忽略對(duì)應(yīng)的(部分)binlog event 數(shù)據(jù)。
binlog event 過(guò)濾在進(jìn)行增量數(shù)據(jù)同步時(shí),有時(shí)會(huì)期望過(guò)濾掉某些特定類(lèi)型的 binlog event,兩個(gè)典型的場(chǎng)景包括:
上游執(zhí)行 TRUNCATE TABLE 時(shí)不希望清空下游表中的數(shù)據(jù)。
上游分表上執(zhí)行 DROP TABLE 時(shí)不希望 DROP 下游合并后的表。
在 DM 中支持根據(jù) binlog event 的類(lèi)型進(jìn)行過(guò)濾,對(duì)于需要過(guò)濾 TRUNCATE TABLE 與 DROP TABLE 的場(chǎng)景,可配置規(guī)則如下:
name-of-filter-rule: ? schema-pattern: "test_*" ? table-pattern: "t_*" ? events: ["truncate table", "drop table"] ? action: Ignore
規(guī)則的匹配模式與 table router、column mapping 類(lèi)似,具體的配置項(xiàng)可閱讀該功能對(duì)應(yīng)的用戶(hù)文檔。
在實(shí)現(xiàn)上,當(dāng)解析 binlog event 獲得庫(kù)名、表名及 binlog event 類(lèi)型后,與配置的規(guī)則進(jìn)行匹配,并在匹配后依據(jù) action 配置項(xiàng)來(lái)決定是否需要進(jìn)行過(guò)濾。有關(guān) binlog event 過(guò)濾功能的具體實(shí)現(xiàn),可以閱讀 TiDB-Tools 下的 binlog-filter pkg 源代碼。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/17872.html
摘要:設(shè)計(jì)從年開(kāi)始提供全量導(dǎo)入工具,它以多線(xiàn)程操作錯(cuò)誤重試斷點(diǎn)續(xù)傳以及修改一些專(zhuān)屬配置來(lái)提升數(shù)據(jù)導(dǎo)入速度。此外,多線(xiàn)程的線(xiàn)上導(dǎo)入也代表資料是亂序插入的,新的數(shù)據(jù)范圍會(huì)與舊的重疊。現(xiàn)時(shí)只支持經(jīng)導(dǎo)出的備份。此外亦同時(shí)將文件分割為大小差不多的區(qū)塊默認(rèn)。 作者:Kenny Chan 簡(jiǎn)介 TiDB-Lightning Toolset 是一套快速全量導(dǎo)入 SQL dump 文件到 TiDB 集群的工具...
摘要:內(nèi)容概要源碼閱讀系列將會(huì)從兩條線(xiàn)進(jìn)行展開(kāi),一條是圍繞的系統(tǒng)架構(gòu)和重要模塊進(jìn)行分析,另一條線(xiàn)圍繞內(nèi)部的同步機(jī)制展開(kāi)分析。更多的代碼閱讀內(nèi)容會(huì)在后面的章節(jié)中逐步展開(kāi),敬請(qǐng)期待。 作者:楊非 前言 TiDB-DM 是由 PingCAP 開(kāi)發(fā)的一體化數(shù)據(jù)同步任務(wù)管理平臺(tái),支持從 MySQL 或 MariaDB 到 TiDB 的全量數(shù)據(jù)遷移和增量數(shù)據(jù)同步,在 TiDB DevCon 2019 正...
閱讀 4361·2021-11-22 09:34
閱讀 2689·2021-11-12 10:36
閱讀 741·2021-08-18 10:23
閱讀 2636·2019-08-30 15:55
閱讀 3110·2019-08-30 15:53
閱讀 2080·2019-08-30 15:44
閱讀 1359·2019-08-29 15:37
閱讀 1400·2019-08-29 13:04