mydumper是一個(gè)多線程、高性能的數(shù)據(jù)邏輯備份、恢復(fù)的工具,相比MySQL自帶的mysqldump提速不少。我下載了0.23的穩(wěn)定版本,閱讀了源碼并總結(jié)了一些使用的心得。
mysqldump是個(gè)單線程的邏輯備份工具,依次一個(gè)個(gè)導(dǎo)出多個(gè)表,沒有一個(gè)并行的機(jī)制。mydumper彌補(bǔ)了這方面的缺陷可以并行的多線程的從表中讀入數(shù)據(jù)并同時(shí)寫到不同的文件里。項(xiàng)目的作者是由一群在sun、fb、skysql的工程師完成的。類似的工具還有mk-parallel-dump。
編譯安裝cmake . make sudo make install 源碼分析根據(jù)流程圖解釋一下mydumper的工作步驟。
解析參數(shù)
使用glib的g_option_context_parse,比libc里的getopt_long簡單多了。
連接目標(biāo)數(shù)據(jù)庫。
通過show processlist來判斷是否有長查詢,如果有長查詢則退出dump,可以通過–long-query-guard加長時(shí)間,或者使用–kill-long-queries殺掉長查詢。
鎖定myisam表。
針對innodb table開啟事務(wù)。
產(chǎn)生3個(gè)消息隊(duì)列(線程ready隊(duì)列、任務(wù)隊(duì)列、myisam表處理完畢隊(duì)列)。
conf.queue = g_async_queue_new(); conf.ready = g_async_queue_new(); conf.unlock_tables= g_async_queue_new();產(chǎn)生指定的線程個(gè)數(shù),–threads可以指定,默認(rèn)是4個(gè)。
GThread **threads = g_new(GThread*,num_threads); struct thread_data *td= g_new(struct thread_data, num_threads); for (n=0; n<num_threads; n++) { td[n].conf= &conf; td[n].thread_id= n+1; threads[n] = g_thread_create((GThreadFunc)process_queue,&td[n],TRUE,NULL); g_async_queue_pop(conf.ready); }dump_database,從DATA_DICTIONARY.TABLES讀取所有表,通過–ignore, –tables-list, regex等過濾條件,產(chǎn)生需要dump的目標(biāo)表列表,分別插入innodb_tables、non_innodb_table、 table_schemas三個(gè)鏈表。
query= g_strdup_printf("SELECT TABLE_NAME, ENGINE, TABLE_TYPE as COMMENT FROM DATA_DICTIONARY.TABLES WHERE TABLE_SCHEMA='%s'", database); .... innodb_tables= g_list_append(innodb_tables, dbt); .... non_innodb_table= g_list_append(non_innodb_table, dbt); .... table_schemas= g_list_append(table_schemas, dbt);dump non-innodb table 把需要導(dǎo)出myisam表加入到任務(wù)隊(duì)列。
for (non_innodb_table= g_list_first(non_innodb_table); non_innodb_table; non_innodb_table= g_list_next(non_innodb_table)) { dbt= (struct db_table*) non_innodb_table->data; dump_table(conn, dbt->database, dbt->table, &conf, FALSE); g_atomic_int_inc(&non_innodb_table_counter); }dump innodb table把需要導(dǎo)出innodb表加入任務(wù)隊(duì)列。
for (innodb_tables= g_list_first(innodb_tables); innodb_tables; innodb_tables= g_list_next(innodb_tables)) { dbt= (struct db_table*) innodb_tables->data; dump_table(conn, dbt->database, dbt->table, &conf, TRUE); }dump schema 把需要導(dǎo)出表結(jié)構(gòu)任務(wù)加入到任務(wù)隊(duì)列。
for (table_schemas= g_list_first(table_schemas); table_schemas; table_schemas= g_list_next(table_schemas)) { dbt= (struct db_table*) table_schemas->data; dump_schema(dbt->database, dbt->table, &conf); g_free(dbt->table); g_free(dbt->database); g_free(dbt); }典型的生產(chǎn)者(主線程)消費(fèi)者(子線程)模式,子線程會(huì)從任務(wù)隊(duì)列里讀取需要處理的表名字和表類型,再通過select * from table_name 讀入數(shù)據(jù)各自寫入到各自的文件。
for(;;) { .... job=(struct job *)g_async_queue_pop(conf->queue); .... switch (job->type) { case JOB_DUMP: .... dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename); .... case JOB_DUMP_NON_INNODB: .... dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename); case JOB_SCHEMA: .... dump_schema_data(thrconn, sj->database, sj->table, sj->filename); }所有導(dǎo)數(shù)據(jù)的任務(wù)加入任務(wù)隊(duì)列之后,會(huì)再加入讓線程退出的任務(wù),讓線程自然退出。
case JOB_SHUTDOWN: g_message("Thread %d shutting down", td->thread_id); if (thrconn) mysql_close(thrconn); g_free(job); mysql_thread_end(); return NULL; break;解除myisam表鎖。
等待子線程退出。
使用導(dǎo)出test database的數(shù)據(jù)
mydumper -h 127.0.0.1 -u root --database test指定某個(gè)目錄
mydumper -h 127.0.0.1 -u root --outputdir=.不導(dǎo)出表結(jié)構(gòu)
mydumper -h 127.0.0.1 -u root --no-schema如果表數(shù)據(jù)是空,還是產(chǎn)生一個(gè)空文件(默認(rèn)無數(shù)據(jù)則只有表結(jié)構(gòu)文件)
mydumper -h 127.0.0.1 -u root --build-empty-files設(shè)置長查詢的上限,如果存在比這個(gè)還長的查詢則退出mydumper,也可以設(shè)置殺掉這個(gè)長查詢
mydumper -h 127.0.0.1 -u root --long-query-guard 200 --kill-long-queries設(shè)置要dump的列表–tables-list,不需要設(shè)置db名字,逗號分割
mydumper -h 127.0.0.1 -u root --tables-list=ddd,zzz通過regex也設(shè)置正則表達(dá),需要設(shè)置db名字
mydumper -h 127.0.0.1 -u root --regex=test.z把單表分成多個(gè)chunks,這個(gè)后面會(huì)講分割的原理
mydumper -h 127.0.0.1 -u root --rows 10000過濾某個(gè)引擎的表
mydumper -h 127.0.0.1 -u root -B test --ignore-engines=innodb詳細(xì)日志
mydumper -h 127.0.0.1 -u root -B test -v 3 幾個(gè)注意點(diǎn)各自線程都要自己連接到數(shù)據(jù)庫,因?yàn)閘ibmysql是線程不安全的。
因?yàn)閷yisam表有有表鎖,所有先處理myisam表,記錄myisam表個(gè)數(shù),每處理一個(gè)myisam都原子操作數(shù)量減一。并在myisam表都處理完畢后,立即解鎖,盡量減少鎖定的時(shí)間,而不是在導(dǎo)出innodb表數(shù)據(jù)的時(shí)候還在lock myisam表。
main_thread
for (non_innodb_table= g_list_first(non_innodb_table); non_innodb_table; non_innodb_table= g_list_next(non_innodb_table)) { dbt= (struct db_table*) non_innodb_table->data; dump_table(conn, dbt->database, dbt->table, &conf, FALSE); g_atomic_int_inc(&non_innodb_table_counter); }child_thread
if (g_atomic_int_dec_and_test(&non_innodb_table_counter) && g_atomic_int_get(&non_innodb_done)) { g_async_queue_push(conf->unlock_tables, GINT_TO_POINTER(1)); }main_thread
g_async_queue_pop(conf.unlock_tables); g_message("Non-InnoDB dump complete, unlocking tables"); mysql_query(conn, "UNLOCK TABLES");–regex的處理在–tables-list后, 先滿足–tables-list再滿足–regex,如下只會(huì)dump表z1
mydumper -h 127.0.0.1 -u root --regex=test.z1 --outputdir=. --rows=10000 -v 3 -e --tables-list=z2,z1 ** Message: Thread 1 dumping data for `test`.`z1` ** Message: Thread 2 dumping schema for `test`.`z1`–rows的使用,設(shè)置–rows可以把一個(gè)表分成多個(gè)文件。分塊的原則并不是根據(jù)–rows設(shè)定的行數(shù)來決定生成文件里包含的函數(shù),而是通過rows和表的總行數(shù)計(jì)算出要生成的文件個(gè)數(shù),盡量保證每個(gè)文件的大小一致。
表的總行數(shù)是如何獲得的?
首先mydumper會(huì)選擇一個(gè)索引,順序是pk、uk或者show index from table里Cardinality最高的一個(gè)索引,再通過explain select index from table的rows字段獲得總行數(shù)total_nums(可能不準(zhǔn)確),于是第一個(gè)文件就是從select * from table where index >=1 and index < total_nums/ (int(total_nums/ rows) – 1) + 1。每個(gè)分塊可以分到不同的線程,所以即便同一個(gè)表dump都可以很快加速。
ps:這個(gè)項(xiàng)目大量使用glib(gnome)比較少見,看了一下glib doc覺得glib設(shè)計(jì)的挺好的用起來很方便,否則實(shí)現(xiàn)一個(gè)消息隊(duì)列加多線程還是要幾百行代碼的。接下來要看mydumper0.50的代碼。
作者: hoterran
原文鏈接地址:http://www.hoterran.info/mydumper_usage
myumper源碼下載地址:http://www.mydumper.org/?p=23
歡迎光臨 Chinaunix (http://72891.cn/) | Powered by Discuz! X3.2 |