1. DataX
数据同步的工具有很多,比如Hadoop和结构化数据存储之间高效批量数据传输的工具Apache Sqoop,借助于 Hadoop集群可以并行的高效传输数据,但是这种方式往往需要依赖于一个Hadoop环境,在生产环境有时我们并没有直接的权限操作这个环境,而是只提供一个HDFS的端口,这时用Sqoop就不是很方便。Oracle数据和MySQL数据的同步工具会想到alibaba / yugong,基于MySQL binlog增量订阅和消费中间件使用起来非常方便,同时也执行消息队里模式(Apache Kafka、Apache RockMQ),但这个主要针对于特定场景和业务下。
DataX是阿里巴巴集团内被广泛使用的离线数据同步工具or平台,它支持MySQL、Oracle、SQLServer、PostgreSQL、HDFS、Hive、HBase、MaxCompute(ODPS)、MongoDB、Elasticsearch等各种异构数据源之间的高效的数据同步。Reader和Writer支持的列表可以访问文档Support Data Channels
使用起来也是非常方便,只需要下载datax.tar.gz压缩包,解压之后就可以使用,不需要设置任何依赖,放置到任何一个开通了数据源和目标端端口权限的节点,然后定义一个JSON文件,在JSON文件中定义Reader和Writer,就可以在本地以多线程并发高效的进行数据之间的同步功能。
- 执行一次数据同步作业称为一个Job,DataX接受到这个Job之后,开始启动一个进程来完成这个Job的同步过程;
- 这个进程会根据不同的源端切分策略,将Job进行切分为多个小的Task,Task就是DataX作业的最小单元,每个Task都负责一部分数据的同步工作,这些Task是并发执行的;
- 切分完数据之后,DataX Job会调用Scheduler模块,根据配置的并发数量,将拆分的Task重新组合,组装为TaskGroup,每一个TaskGroup负责以一定的并发执行Task,默认单个TaskGroup的并发数为5;
- Task启动后会固定启动Reader -> Channel -> Writer的线程来完成任务同步工作;
- Task运行时,Job开始监控并等待各个TaskGroup模块任务完成,等所有TaskGroup成功完成后Job进程才退出,否则异常退出。
例子:比如现在有1个100张分表,需要将数据从MySQL同步到odps,在json中设置了20个并发,DataX的调度决策如下:
- DataX Job将表拆分为100个Task;
- 由20个并发,可以计算出需要4个TaskGroup(一个TaskGroup运行5个Task);
- 将100个任务平均分配个4个TaskGroup,也就是说每个TaskGroup会负责5个并发运行25个Task。
2. ODPS同步数据到HDFS
ODPS现在已经更名为MaxCompute,它是一种大数据计算服务,一个快速、完全托管的TB/PB级数据仓库解决方案,具体可以查看官方文档MaxCompute。
在content中的reader处定义一个odpsreader,writer定义为hdfswriter。这里需要注意的是HdfsWriter插件目前只支持ORC和TEXT,而 HdfsReader插件目前支持ORC、 TEXT、CSV、SEQUENCE、RC。
如果我们在写入HDFS时文件类型只有两个选择,要么write的文件类型是TEXT,要么是ORC格式,如果想要对数据进行压缩可以选择ORC格式文件,这个确实能够对数据进行非常有效的压缩(一个原本165MB的文件,压缩后约为20MB),在数据量较大时能很有效的节省空间,在Hive建表时指定文件存储格式STORED AS orc
即可高效地使用数据,但是比较尴尬的是如果使用的是CDH平台上的Impala来操作Hive的数据,在Cloudera Enterprise 6.0.x是不支持ORC格式的,不过在随后的Cloudera Enterprise 6.1.x支持了ORC格式,有这方面业务的需要考虑下这种特殊情况。
注意:如果reader是ODPS,字段信息的定义顺序没有限制,但尽量和writer中定义的字段信息的顺序一致,或者writer中定义字段信息时要和reader处的顺序保持一致。字段名不要包含空字符,否则DataX验证类型时会报错。
{
"job": {
"setting": {
"speed": {
"channel": "3"
}
},
"content": [
{
"reader": {
"name": "odpsreader",
"parameter": {
"accessId": "3oL**********BDZ",
"accessKey": "Myk********************WY9UTly",
"project": "targetProjectName",
"table": "tableName",
"column": [
"*"
],
"partition": [
"pt=20141010000000,year=2014"
],
"odpsServer": "http://xxx/api",
"tunnelServer": "http://xxx",
"splitMode": "record"
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://cdh1:8020",
"fileType": "TEXT",
"path": "/user/hive/warehouse/kudu_test.db/dataxtype_test",
"fileName": "xxxx",
"writeMode": "append",
"fieldDelimiter": "?",
"column": [
{
"name": "f1",
"type": "BIGINT"
},
{
"name": "f2",
"type": "STRING"
},
{
"name": "f3",
"type": "BOOLEAN"
},
{
"name": "f4",
"type": "DOUBLE"
},
{
"name": "f5",
"type": "FLOAT"
},
{
"name": " f6",
"type": "TIMESTAMP"
}
]
}
}
}
]
}
}
注意:在writer
中的column
一定要对读取的每个字段定义字段名和字段类型。关于分隔符,可以使用一些非常特殊的单字符,比如输入法中提供的特殊字符(最好测试一下,有些虽然是单字符但也不支持),如果想使用多字符作为分隔符,可以查看我的另一篇blog Hive中的自定义分隔符。
在writer中会定义path
和fileName
,这里可能有些人会有个疑问,就是为什么定义fileName
,这个文件名并不是严格的文件名,只是文件名的一个前缀标识,多个并行时会生成fileName__随机字符串
的文件。
3. HDFS同步数据到另一个HDFS
将下面json保存为 xxx.json,然后执行 $DATAX_HOME/bin/datax.py xxx.json
。
注意:reader定义字段类型时一定要和HDFS上的文件的列的顺序一致(也就是建表时字段的顺序),writer时和reader列顺序一致。
{
"job": {
"setting": {
"speed": {
"channel": 1
}
},
"content": [
{
"reader": {
"name": "hdfsreader",
"parameter": {
"path": "/user/hive/warehouse/hive_test.db/dataxtype_test/*",
"defaultFS": "hdfs://one-hdfs:8020",
"fileType": "text",
"encoding": "UTF-8",
"fieldDelimiter": "|",
"column": [
{
"type": "Long",
"index": "0"
},
{
"type": "String",
"index": "1"
},
{
"type": "Boolean",
"index": "2"
},
{
"type": "Double",
"index": "3"
},
{
"type": "Double",
"index": "4"
},
{
"type": "Date",
"index": "5"
}
]
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://cdh1:8020",
"fileType": "TEXT",
"path": "/user/hive/warehouse/kudu_test.db/dataxtype_test",
"fileName": "xxxx",
"writeMode": "append",
"fieldDelimiter": "?",
"column": [
{
"name": "f1",
"type": "BIGINT"
},
{
"name": "f2",
"type": "STRING"
},
{
"name": "f3",
"type": "BOOLEAN"
},
{
"name": "f4",
"type": "DOUBLE"
},
{
"name": "f5",
"type": "FLOAT"
},
{
"name": " f6",
"type": "TIMESTAMP"
}
]
}
}
}
]
}
}
4. MongoDB同步数据到HDFS
将下面json保存为 xxx.json,然后执行 $DATAX_HOME/bin/datax.py xxx.json
。如果测试数据可以先导到本地"defaultFS": "file:///",
如果格式没问题再改为"defaultFS": "hdfs://bigdata001:8020",
。或者因为端口问题只能通过某个服务上传,可以直接将远程连接读入MongoDB的数据通过FTP方式发送到指定服务上,这里只需将hdfswriter改为ftpwriter。
{
"job": {
"setting": {
"speed": {
"channel": "3"
}
},
"content": [
{
"reader": {
"name": "mongodbreader",
"parameter": {
"address": [
"xx.xxx.x.xx:27017"
],
"userName": "",
"userPassword": "",
"dbName": "tag_per_data",
"collectionName": "tag_data12",
"column": [
{
"name": "_id",
"type": "string"
},
{
"name": "r_cert_no",
"type": "string"
},
{
"name": "r_name",
"type": "string"
},
{
"name": "b_cert_no",
"type": "string"
},
{
"name": "b_name",
"type": "string"
},
{
"name": "r_flag",
"type": "string"
},
{
"name": "body_md5",
"type": "Array",
"spliter": ""
}
]
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "file:///",
"fieldDelimiter": "╬",
"fileName": "m_cpws_info_w3",
"fileType": "orc",
"path": "/root/mongo_data/m_cpws_info_w3",
"writeMode": "append",
"column": [
{
"name": "_id",
"type": "string"
},
{
"name": "r_cert_no",
"type": "string"
},
{
"name": "r_name",
"type": "string"
},
{
"name": "b_cert_no",
"type": "string"
},
{
"name": "b_name",
"type": "string"
},
{
"name": "r_flag",
"type": "string"
},
{
"name": "body_md5",
"type": "string"
}
]
}
}
}
]
}
}
Info:但在MongoDB右密码时,用但在MongoDB右密码时,用$MONGO_HOME/bin/mongo --host xx.xxx.x.xx --port 27017 -u "用户名" -p "密码" --authenticationDatabase "admin"
可以正常访问给定的库,但是在上述mongodbreader
中配置上用户名和免密依然没有权限访问。
这是可以临时这样迁移数据:
# 登陆远程MongoDB
$MONGO_HOME/bin/mongo --host xx.xxx.x.xx --port 27017 -u "用户名" -p "密码" --authenticationDatabase "admin"
# 切换到给定库下
use 库名
# 查看表字段信息,不显示 details 字段值
db.getCollection("表名").find({},{details: 0}).limit(1)
然后用MongoDB自带的数据导出工具mongoexport导出数据为csv文件
$MONGO_HOME/bin/mongoexport --host xx.xxx.x.xx --port 27017 \
-d 库名 -c 表名 -u "用户名" -p "密码" --authenticationDatabase "admin" \
-o ./data/表名.csv --type csv \
-f "_id,field_01,field_02,field_03,……"
也许这个数据文件非常大,可以对这个文件进行切分,使用Linux的sed
命令。
#每100万条数据切分为一个文件
sed -n '1,1000000'p axd_tao_ord.csv >> axd_tao_ord_0000001-1000000.csv
sed -n '1000001,2000000'p axd_tao_ord.csv >> axd_tao_ord_1000001-2000000.csv
sed -n '2000001,3000000'p axd_tao_ord.csv >> axd_tao_ord_2000001-3000000.csv
……
# 这里也可以使用split命令
split -l 1000000 axd_tao_ord.csv axd_tao_ord_