大家还记得高性能多线程的MySQL备份恢复工具Mydumper 吧,一个非常不错的工具,大概的介绍可以看这里
本文是转自@hoterran同学的一篇博文http://www.hoterran.info/mydumper_usage,对Mydumper稳定版v0.23源码做了一次分析,以及一些常用的使用命令,很有含金量的一篇博文,继续往下看吧。
mydumper是一个多线程、高性能的数据逻辑备份、恢复的工具,相比MySQL自带的mysqldump提速不少。我下载了0.23的稳定版本,阅读了源码并总结了一些使用的心得。
mysqldump是个单线程的逻辑备份工具,依次一个个导出多个表,没有一个并行的机制。mydumper弥补了这方面的缺陷可以并行的多线程的从表中读入数据并同时写到不同的文件里。项目的作者是由一群在sun、fb、skysql的工程师完成的。类似的工具还有mk-parallel-dump

编译安装

cmake .
make
sudo make install

源码分析

根据流程图解释一下mydumper的工作步骤。
Mydumper的使用和源代码分析_职场
解析参数
使用glib的g_option_context_parse,比libc里的getopt_long简单多了。
连接目标数据库。
通过show processlist来判断是否有长查询,如果有长查询则退出dump,可以通过–long-query-guard加长时间,或者使用–kill-long-queries杀掉长查询。
锁定myisam表。
针对innodb table开启事务。
产生3个消息队列(线程ready队列、任务队列、myisam表处理完毕队列)。
conf.queue = g_async_queue_new();
conf.ready = g_async_queue_new();
conf.unlock_tables= g_async_queue_new();
产生指定的线程个数,–threads可以指定,默认是4个。
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等过滤条件,产生需要dump的目标表列表,分别插入innodb_tables、non_innodb_table、table_schemas三个链表。
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 把需要导出myisam表加入到任务队列。
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把需要导出innodb表加入任务队列。
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 把需要导出表结构任务加入到任务队列。
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);
}
典型的生产者(主线程)消费者(子线程)模式,子线程会从任务队列里读取需要处理的表名字和表类型,再通过select * from table_name 读入数据各自写入到各自的文件。
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);
}
所有导数据的任务加入任务队列之后,会再加入让线程退出的任务,让线程自然退出。
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表锁。
等待子线程退出。

使用

导出test database的数据
mydumper -h 127.0.0.1 -u root –database test
指定某个目录
mydumper -h 127.0.0.1 -u root –outputdir=.
不导出表结构
mydumper -h 127.0.0.1 -u root –no-schema
如果表数据是空,还是产生一个空文件(默认无数据则只有表结构文件)
mydumper -h 127.0.0.1 -u root –build-empty-files
设置长查询的上限,如果存在比这个还长的查询则退出mydumper,也可以设置杀掉这个长查询
mydumper -h 127.0.0.1 -u root –long-query-guard 200 –kill-long-queries
设置要dump的列表–tables-list,不需要设置db名字,逗号分割
mydumper -h 127.0.0.1 -u root –tables-list=ddd,zzz
通过regex也设置正则表达,需要设置db名字
mydumper -h 127.0.0.1 -u root –regex=test.z
把单表分成多个chunks,这个后面会讲分割的原理
mydumper -h 127.0.0.1 -u root –rows 10000
过滤某个引擎的表
mydumper -h 127.0.0.1 -u root -B test –ignore-engines=innodb
详细日志
mydumper -h 127.0.0.1 -u root -B test -v 3

几个注意点

各自线程都要自己连接到数据库,因为libmysql是线程不安全的。
因为对myisam表有有表锁,所有先处理myisam表,记录myisam表个数,每处理一个myisam都原子操作数量减一。并在myisam表都处理完毕后,立即解锁,尽量减少锁定的时间,而不是在导出innodb表数据的时候还在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,如下只会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的使用,设置–rows可以把一个表分成多个文件。分块的原则并不是根据–rows设定的行数来决定生成文件里包含的函数,而是通过rows和表的总行数计算出要生成的文件个数,尽量保证每个文件的大小一致。
表的总行数是如何获得的?
首先mydumper会选择一个索引,顺序是pk、uk或者show index from table里Cardinality最高的一个索引,再通过explain select index from table的rows字段获得总行数total_nums(可能不准确),于是第一个文件就是从select * from table where index >=1 and index < total_nums/ (int(total_nums/ rows) – 1) + 1。每个分块可以分到不同的线程,所以即便同一个表dump都可以很快加速。
ps:这个项目大量使用glib(gnome)比较少见,看了一下glib doc觉得glib设计的挺好的用起来很方便,否则实现一个消息队列加多线程还是要几百行代码的。接下来要看mydumper0.50的代码。
 

Mydumper: 高性能多线程MySQL备份恢复工具

在微博上看到@ningoo同学提到的mysql多线程dump工具: Mydumper ,于是乎去mydumper官网下载了,安装试用了下。
Mydumper是一个针对MySQL和Drizzle高性能多线程备份和恢复工具,3个主要的开发人员分别来自MySQL,Facebook,SkySQL公司。目前已经在有一些大型产品业务上测试并使用了Mydumper。目前有稳定版和开发版两种。
Mydumper的主要特性
  • 轻量级C语言写的代码
  • 比mysqldump接近快10倍的速度
  • 事务性和非事务性表一致的快照(适用于0.22+)
  • 快速的文件压缩(官网用on-the-fly来形容)
  • 可以导出binlog
  • 实用的多线程恢复
  • 可以用守护进程的工作方式,定时的扫描和输出连续的二进制日志
  • 当然还有个就是开源咯~
安装Mydumper
wget http://launchpad.net/mydumper/0.5/0.5.0/+download/mydumper-0.5.0.tar.gz
tar zxvf mydumper-0.5.0.tar.gz
cmake .
make
make install
备份工具:mydumper
mydumper -u root -p ” -P 3307 -S /tmp/mysql.3307.sock -B zs -T movie_databases -o /home/zhangsheng/dumper
上述操作,就是将zs库中的movie_databases表导出到/home/zhangsheng/dumper目录中,具体命令可以在官网上查查,特别简单。在/home/zhangsheng/dumper中,发现了2个文件:
zs.movie_databases-schema.sql #schema语句
zs.movie_databases.sql #insert的数据语句,如果指定–rows的话,会将数据分成指定的份数
我们可以去官方文档上关于输出文件一部分看到:除了这两种数据,还有一个Metadata文件(主要是记录主辅同步是binlog的起止时间和日志的postion)和binlog日志文件
恢复工具:myloader
myloader -u root -p ” -P 3307 -S /tmp/mysql.3307.sock -d /home/zhangsheng -B zs
上述操作,就是将刚刚备份到/home/zhangsheng/dumper目录中数据导入到zs库中的movie_databases表。
总体来说,在小量的使用情况下,刚刚5W条数据,和mysqldump相比要明显快很多,真要用到实际环境下,则需要用户做进一步的效率和数据的准确性的验证,再决定是否采用。欢迎拍砖,Have fun ~

MySQL 日志文件

可以执行mysqld –verbose –help | grep log 查看mysqld各种log配置选项
mysql的日志文件目录,如果没有特使指定目录,就在数据目录中,启动mysqld –datadir指定的目录。
1.error log
启动mysqld –log-error=指定,或者cnf配置文件mysqld节点中用log-error指定,默认没指定是自己的hostname.err文件,如下:
2.bin log
下次总结replication是再聊.
3.update log
启动mysqld 指定–log-update=指定
mysql5.0以前版本update log和bin log功能类似,只不过不是用二进制文件记录而是用文本格式记录,mysql5.0以上的版本不支持更新日志。
4.query log
启动mysqld 指定–log=指定
包括了所有query日志,体积比较大,除非需要定位和跟踪特殊query的时候可以短暂打开使用,其他情况慎用。
5.slow query log
纪录所有查询时间超过long_query_time或不使用索引的查询动作。你可以在mysqld的的数据目录中找到所有的纪录档。
slow_query_log = 1
slow_query_log_file = slow_log_location
慢查询日志文件如果较大的话,用mysqldumpslow工具来分析日志文件,可参考实例:http://my.chinaunix.net/space.php?uid=9950859&do=blog&id=122259
6.innodb redo log
innodb是一个事务安全存储引擎,redo log通过记录redo,undo信息,来确保在任何情况下事务的安全性。详细配置在后续总结innodb时再聊.

自己写脚本快速连接mysql和mysqladmin

由于mysql中对内置的工具类似mysql,mysqladmin支持对配置文件的解析,为了避免每次都写一堆的命令行来执行,比如连接mysql,要写一堆-u -p -h之类的参数比较麻烦,写在配置文件里面统一调用是中很好的方式。
比如配置文件my.3308.cnf部分内容如下:
[mysql]
user = root
password = password
socket = /tmp/mysql.3308.sock
port = 3308
[mysqladmin]
user = root
password = password
socket = /tmp/mysql.3308.sock
port = 3308
可以这样调用:
/usr/local/mysql/bin/mysql –defaults-file=/data/mysql/cnf/my.3308.cnf
/usr/local/mysql/bin/mysqladmin –defaults-file=/data/mysql/cnf/my.3308.cnf processlist
其实有时候配置文件cnf里面的东西属于公用的东西,多人开发和维护的情况下,修改配置还是比较麻烦的,特别是自己调试的时候,那就自己写配置、外部加载方式实现吧。
/usr/local/mysql/bin/mysql –defaults-extra-file=/data/config/my.3308.txt #自定义配置文件,格式和上述一致即可
/usr/local/mysql/bin/mysqladmin –defaults-extra-file=/data/config/my.3308.txt processlist #自定义配置文件,格式和上述一致即可
如果一个服务器安装了启用了好几个mysql,同时管理多个mysql,即可将调用命令写成一个shell脚本,放在常用的目录下更方面操作。
比如目录:/home/yourdir
写个mysql.sh和mysqladmin.sh,支持传参调用,如下:
./mysql.sh 3308就连接端口号3308的mysql,./mysqladmin 3306 ping 就检查3306端口server运行状态,其他类推
mysql.sh和mysqladmin.sh脚本如下:
#!/bin/bash
/usr/local/mysql/bin/mysql –defaults-file=/data/mysql/cnf/my.$1.cnf
===================
#!/bin/bash
/usr/local/mysql/bin/mysqladmin –defaults-file=/data/mysql/cnf/my.$1.cnf $2
自定义配置文件原理一样。
写到这里,类似mysql、mysqladmin的mysql自带工具都可以做成这样的快速连接方式。Have fun~

同一服务器运行多个mysql

1.安装多个mysql
这种方式需要注意的是确保不同的安装目录,不同的端口号,不同的Unix套接字文件(.sock文件),如编译时可以这样指定:
./configure –with-tcp-port=3307 \
–with-unix-socket-path=/tmp/mysql.3307.sock\
–prefix=/usr/local/mysql
2.不编译新的MySQL,用不同的sock文件和端口号启动不同的mysql服务,同时注意数据文件存放到不同的目录,不然会冲突
./bin/mysql_install_db –user=mysql \
–basedir=/usr/local/mysql \
–datadir=/data/mysql/data/3307
chown -R mysql.mysql /data/mysql/data/3307
./mysqld_safe –socket=/tmp/mysql.3307.sock –port=3307 –datadir=/data/mysql/data/3307 –pid-file=/data/mysql/logs/3307/mysql.pid log-error=/data/mysql/logs/3307/mysql_error.log –user=mysql &
或者写成配置文件,加载配置文件方式启动
./mysqld_safe –defaults-file=/data/mysql/cnf/mysql.3307.cnf &
简单配置如下:
[mysqld]
user = mysql
port = 3307
socket = /tmp/mysql.3307.sock
basedir = /usr/local/mysql
datadir = /data/mysql/data/3307
log-error = /data/mysql/logs/3307/mysql_error.log
pid-file = /data/mysql/logs/3307/mysql.pid
3.使用mysqld_multi启动/停止多个mysql服务
这里有个例子 http://hi.baidu.com/willwillyes/blog/item/e8066bec980968d02f2e219c.html
不过是在一个配置文件完成,如果不同的服务需要不同的配置,最好把不同mysql服务的配置文件单独出来,特别是有些服务正在运行的情况下,容易影响,像第二种方式中单独配置文件一样.
重新写一个给mysqld_multi加载的配置文件,如:/data/mysql/cnf/my.cnf
[mysqld_multi]
mysqld = /usr/local/mysql/bin/mysqld_safe
mysqladmin = /usr/local/mysql/bin/mysqladmin
user = root
password = password
log = /data/mysql/logs/mysqld_multi.log
[mysqld1]
defaults-file = /data/mysql/cnf/my.3306.cnf
[mysqld2]
defaults-file = /data/mysql/cnf/my.3307.cnf
mysqld_multi启动多个mysql方式很简单,如下
./mysqld_multi –defaults-extra-file=/data/mysql/cnf/my.cnf start
详细说明可参考官方手册 mysqld_multi:管理多个MySQL服务器的程序
但是到这里,似乎进展都很顺利,但是stop的时候,却mysqladmin shutdown操作失败,错误提示mysqladmin连接找不到sock文件问题啊,明明配置文件里面都配置好了,不然启动都起不起来,无奈,我将[mysqld1]和[mysqld2]下的内容用/data/mysql/cnf/my.3306.cnf和/data/mysql/cnf/my.3307.cnf里面的[mysqld]替换,结果神奇般的OK,能start和stop。
那到底为什么会这样呢? ./mysqld_multi –example里面也是推荐将各个[mysqldN]列举出来,为什么读配置文件加载方式只能启动,不能停止,难道mysqld_multi是调用mysqld_safe和mysqladmin命令的方式不一样造成的?另外命令行传递参数defaults-extra-file或者defaults-file时,必须放在最前面,难道是受这个这个地方的影响?有点不解?
【另外】
运行mysqld_multi遇到提示错误: WARNING! my_print_defaults command not found!
解决办法:#export PATH=$PATH:/usr/local/mysql/bin


出至:
http://www.domysql.com/topics/mysql%E6%9C%8D%E5%8A%A1