一、概述
Hadoop的版本更新挺快的,已經到了2.4,但是其周邊工具的更新速度還是比較慢的,一些舊的周邊工具版本對hadoop2.x的兼容性做得還不完善,特別是sqoop。最近,在爲hadoop2.2.0找適合的sqoop版本時遇到了很多問題。嘗試了多個sqoop1.4.x版本的直接簡單粗暴的報版本不兼容問題,其中測了sqoop-1.4.4.bin__hadoop-0.23這個版本,在該版本中直接用sqoop的腳本export HDFS的數據是沒有問題的,但是一旦調用JAVA API來進行對HDFS的數據的export的時候就各種不兼容問題,原因是這個版本的API也是基於hadoop1.x來寫的。另外還嘗試了使用sqoop2(之前blog寫過關於sqoop2的部署和使用情況:http://zengzhaozheng.blog.51cto.com/8219051/1431882 ),這個版本取消了sqoop1的腳本執行方式,可以採取交互式、api或者rest的方式工作,但是我在使用的過程中還是存在的一些問題:sqoop2(我用的是1.99.3)無法指定列的分隔符、對\N等字符的處理有問題、對列值的類型判斷存在問題等(其詳細問題所在請看,sqoop1.99.3源代碼的org.apache.sqoop.job.io.Data類)。
這個禮拜終於找到了一個比較好的方案來取代sqoop作爲HDFS到mysql的數據export模塊,那就是大淘寶開源的datax。雖然datax採用的是單機方式的作業方式,但是經過試驗我對比了一下其和sqoop性能上的差異,在數據量不是特別大的情況下datax和sqoop的性能相差不是很明顯的,在少量數據的情況下datax的性能稍微好點。
這篇blog將簡單介紹一下這個datax這個框架以及它的用法,特別地說說如果修改datax才能使得datax運行在hadoop2.x上(datax是基於hadoop1.x進行開發的)。另外,主要和大家分享一下我在自己項目中如何使用datax,如何通過自己編寫的shell腳本將datax、mysql和項目粘合起來。
二、datax簡介和datax在hadoop2.x上的兼容部署
1、datax簡介
DataX是一個在異構的數據庫/文件系統之間高速交換數據的工具,實現了在任意的數據處理系統(RDBMS/Hdfs/Local filesystem)之間的數據交換。Datax框架中我最欣賞的就是基於插件的模式,你在部署的時候可以只安裝那些用到的Reader/Writer插件rpm包,沒有用的可以不用安裝。同時,你也可以根據自己的特殊需求很快的寫出Reader、Writer。Datax採用Framework + plugin架構構建,Framework處理了緩衝,流控,併發,上下文加載等高速數據交換的大部分技術問題,提供了簡單的接口與插件交互,插件僅需實現對數據處理系統的訪問。Datax的運行方式採用stand-alone方式,在數據傳輸過程在單進程內完成,全內存操作,不讀寫磁盤,也沒有IPC通信。下面是一個來自大淘寶開源官網的datax架構圖:
各個組件的作用:
Job: 一道數據同步作業
Splitter: 作業切分模塊,將一個大任務與分解成多個可以併發的小任務.
Sub-job: 數據同步作業切分後的小任務
Reader(Loader): 數據讀入模塊,負責運行切分後的小任務,將數據從源頭裝載入DataX
Storage: Reader和Writer通過Storage交換數據
Writer(Dumper): 數據寫出模塊,負責將數據從DataX導入至目的數據地
Datax內置插件:
DataX框架內部通過雙緩衝隊列、線程池封裝等技術,集中處理了高速數據交換遇到的問題,提供簡單的接口與插件交互,插件分爲Reader和Writer兩類,基於框架提供的插件接口,可以十分便捷的開發出需要的插件。比如想要從oracle導出數據到mysql,那麼需要做的就是開發出OracleReader和MysqlWriter插件,裝配到框架上即可。並且這樣的插件一般情況下在其他數據交換場合是可以通用的。更大的驚喜是我們已經開發瞭如下插件:
Reader插件
hdfsreader : 支持從hdfs文件系統獲取數據。
mysqlreader: 支持從mysql數據庫獲取數據。
sqlserverreader: 支持從sqlserver數據庫獲取數據。
oraclereader : 支持從oracle數據庫獲取數據。
streamreader: 支持從stream流獲取數據(常用於測試)
httpreader : 支持從http URL獲取數據。
Writer插件
hdfswriter:支持向hdbf寫入數據。
mysqlwriter:支持向mysql寫入數據。
oraclewriter:支持向oracle寫入數據。
streamwriter:支持向stream流寫入數據。(常用於測試)
2、datax在hadoop2.x上的兼容部署
Datax是基於Hadoop1.x開發的,因此要想基於HADOOP2.x使用hdfsreader和hdfswriter插件,那麼必須對這些插件的本地庫以及一些jar包替換掉,同時要增加Hadoop2.x所需的依賴包,下面以hdfsreader爲例說明:
進入到plugins目錄找到hdfsreader,將hadoop-0.19.2-core.jar刪除,將本地庫替換爲$HAOOP_HOME2.x/lib/native/libhadoop.so。同時添加Hadoop2.x的依賴包,如下圖:
另外,Datax需要hadoop1.x的hadoop-core.xml配置文件,但是hadoop2.x中不存在這個文件,這裏有一個解決方法,就是將各個配置文件的配置項都集中寫到一個新建的配置文件中,單獨有datax使用,這個配置文件在datax的job xml文件由參數hadoop-conf配上。到現在爲止,datax與hadoop2.x的兼容性修改已經完成了。
還要做其他環境的調整,確保java版本>=1.6,python的版本>=2.6(對於python的版本選擇上,個人推薦2.6或者2.7,如果pytyon版本上到3.x的話會有錯誤,個人經驗)。最後修改一下各個插件的rpm包的build路徑:
下面以t_dp_datax_engine.spec爲例子:
上面紅色方框的地方是指build rpm 插件後新產生的文件夾位置,改爲自己編輯的目錄。
下面以t_dp_datax_engine.spec爲例子,看看怎麼build rpm 插件:
具體執行過程如下:
1、請先check out一份DataX源碼,並cd切換到DataX源碼中的rpm目錄
2、編譯打包DataX engine包,使用rpmbuild --ba t_dp_datax_engine.spec(請確保有root權限),打包生成的rpm後如下圖所示
Rpm製作完成後,即可分發、安裝,例如使用
rpm -ivh t_dp_datax_engine.rpm
即可安裝DataX engine 包,需要注意的是engine的rpm地址源自於上圖的截圖中信息。
如下圖:
安裝完成後,在/home/taobao/datax/目錄下會存在如下文件:
其他的插件按照這種方式按照好就ok了。
三、datax的實際應用記錄分享
在blog的這部分主要分享一下我對datax使用的一個小案例,希望能夠給初用datax的同學一點點參照。
具體業務場景:
需要將存儲在HDFS上的一些表export到mysql中,不希望datax對每一個表的export操作都產生一個job xml文件,希望對不同的表動態使用同一個 job xml文件(這個用datax配置文件動態參數結合shell實現)。同時,根據公司業務的需求當不同的HDFS 表export到mysql的前後還需要做一些基於mysql的DML操作(這個可以通過datax 配置文件中的pre以及post參數進行配置,但是我爲了方便流程的控制用shell取代了)。
實現步驟:
步驟1:
執行$DATAX_HOME/bin/datax.py -e命令,選擇data source來源,這裏我們選擇7:
接着選擇export的目標源,這個我們選擇0:
步驟2:
根據自己的業務需求和HADOOP的相應環境配置產生的job xml,進入到$DATAX_HOME/jobs,編輯job配置文件,我的配置如下(裏邊的一些動態參數有下面我自己寫的Shell中進行控制):
<?xml version="1.0" encoding="UTF-8"?> <jobs> <job id="hdfsreader_to_mysqlwriter_job"> <reader> <plugin>hdfsreader</plugin> <!-- description:HDFS login account, e.g. 'username, groupname(groupname...),#password mandatory:true name:ugi --> <param key="hadoop.job.ugi" value="hadoop,supergroup#jpkjcluster"/> <!-- description:hadoop-site.xml path mandatory:false name:hadoop_conf --> <param key="hadoop_conf" value="/data/hadoop/hadoop-2.2.0/etc/hadoop/datax_hadoop_conf.xml"/> <!-- description:hdfs path, format like: hdfs://ip:port/path, or file:///home/taobao/ mandatory:true name:dir --> <param key="dir" value="hdfs://172.16.8.1:8020/user/hive/warehouse/jl.db/${hdfs_table}/day=${export_day}"/> <!-- default:\t description:how to sperate a line mandatory:false name:fieldSplit --> <param key="field_split" value=","/> <!-- default:UTF-8 range:UTF-8|GBK|GB2312 description:hdfs encode mandatory:false name:encoding --> <param key="encoding" value="UTF-8"/> <!-- default:4096 range:[1024-4194304] description:how large the buffer mandatory:false name:bufferSize --> <param key="buffer_size" value="4096"/> <!-- default:\N range: description:replace the nullstring to null mandatory:false name:nullString --> <param key="nullstring" value="\N"/> <!-- default:true range:true|false description:ingore key mandatory:false name:ignoreKey --> <param key="ignore_key" value="true"/> <!-- default: range: description:how to filter column mandatory:false name:colFilter <param key="col_filter" value="?"/> --> <!-- default:1 range:1-100 description:concurrency of the job mandatory:false name:concurrency --> <param key="concurrency" value="${reader_concurrency}"/> </reader> <writer> <plugin>mysqlwriter</plugin> <!-- description:Mysql database ip address mandatory:true name:ip --> <param key="ip" value="jl-master"/> <!-- default:3306 description:Mysql database port mandatory:true name:port --> <param key="port" value="3306"/> <!-- description:Mysql database name mandatory:true name:dbname --> <param key="dbname" value="newidigg_jilin"/> <!-- description:Mysql database login username mandatory:true name:username --> <param key="username" value="hadoop"/> <!-- description:Mysql database login password mandatory:true name:password --> <param key="password" value="jpkjcluster"/> <!-- default: range: description:table to be dumped data into mandatory:true name:table --> <param key="table" value="${mysql_table}"/> <!-- range: description:order of columns mandatory:false name:colorder <param key="colorder" value="?"/> --> <!-- default:UTF-8 range:UTF-8|GBK|GB2312 description: mandatory:false name:encoding --> <param key="encoding" value="UTF-8"/> <!-- description:execute sql before dumping data mandatory:false name:pre <param key="pre" value="${preSql}"/> --> <!-- description:execute sql after dumping data mandatory:false name:post <param key="post" value="${postSql}"/> --> <!-- default:0 range:[0-65535] description:error limit mandatory:false name:limit --> <param key="limit" value="0"/> <!-- mandatory:false name:set <param key="set" value="?"/> --> <!-- default:false range:[true/false] mandatory:false name:replace --> <param key="replace" value="false"/> <!-- range:params1|params2|... description:mysql driver params mandatory:false name:mysql.params <param key="mysql.params" value="?"/> --> <!-- default:1 range:1-100 description:concurrency of the job mandatory:false --> <param key="concurrency" value="${writer_concurrency}"/> </writer> </job> </jobs>
步驟3:
編寫Shell腳本export_hdfs2mysql.sh對整個Datax作業根據業務需求進行控制:
#!/bin/bash #author:曾昭正 #create time:2014-08-14 workspace=`dirname $0` dataxHome='/data/hadoop/datax' export_day=$1 reader_concurrency=1 writer_concurrency=1 mysqlUser='hadoop' mysqlPassword='jpkjcluster' mysqlServerHost='jl-master' currentDatabase='newidigg_jilin' preSql='' postSql='' importTable=('tb_userview_domain_noMdn' 'tb_fact_app_v2' 'tb_fact_domain' 'tb_fact_tag' 'tb_fact_top5_www' 'tb_fact_upwww_time' \ 'tb_fact_search' 'tb_userview_domain' 'tb_userview_kpi_order' 'tb_userview_search' 'tb_userview_time' 'tb_userview_tag'); #function which is used to DDL or DML msyql function mysqlController(){ #這裏注意一下:這裏的$1不同於整個腳本的參數$1,這裏是指函數的第一個參數 local sqlString=$* echo `date +%Y-%m-%d" "%H:%M:%S` "執行:${sqlString}" mysql -u ${mysqlUser} --password=${mysqlPassword} -h ${mysqlServerHost} -e " use ${currentDatabase}; ${sqlString}; " } #通用表導入模塊 function commonImport(){ local current_table=$1 #create temporary table before importing data into mysql. echo `date +%Y-%m-%d" "%H:%M:%S` "......進入處理${current_table}表入mysql庫環節......" echo `date +%Y-%m-%d" "%H:%M:%S` "入庫前創建臨時表" preSql="drop table if exists ${current_table}_${export_day};create table ${current_table}_${export_day} like ${current_table}" mysqlController ${preSql} #import data from hdfs into msyql. echo `date +%Y-%m-%d" "%H:%M:%S` "將hdfs的${current_table}表導入mysql...." #調整mysql的導入線程數 writer_concurrency=2 #調用Datax將hdfs文件導入mysql python ${dataxHome}/bin/datax.py ${dataxHome}/jobs/hdfsreader_to_mysqlwriter_1407525566122.xml -p"-Dhdfs_table=${current_table} -Dexport_day=${export_day} -Dreader_concurrency=${reader_concurrency} -Dwriter_concurrency=${writer_concurrency} -Dmysql_table=${current_table}_${export_day}" #Updata Data Relationship after importing data into mysql. if [ ${current_table} == "tb_userview_search" ] then postSql="drop table if exists ${current_table}; \ rename table ${current_table}_${export_day} to ${current_table}; \ CREATE INDEX mdn_index ON ${current_table}(mdn); " else postSql="drop table if exists ${current_table}; \ rename table ${current_table}_${export_day} to ${current_table}; \ Alter table ${current_table} add primary key(mdn); " fi mysqlController ${postSql} echo `date +%Y-%m-%d" "%H:%M:%S` "......完成處理${current_table}表入mysql操作......" } for tableItem in ${importTable[*]} do if [ ${tableItem} == "tb_userview_domain" -o ${tableItem} == "tb_userview_kpi_order" -o ${tableItem} == "tb_userview_search" -o ${tableItem} == "tb_userview_time" -o ${tableItem} == "tb_userview_tag" ] then commonImport ${tableItem} else #delete dirty data preSql="delete from ${tableItem} where day_id=${export_day};" mysqlController ${preSql} #調用Datax將hdfs文件導入mysql python ${dataxHome}/bin/datax.py ${dataxHome}/jobs/hdfsreader_to_mysqlwriter_1407525566122.xml -p"-Dhdfs_table=${tableItem} -Dexport_day=${export_day} -Dreader_concurrency=${reader_concurrency} -Dwriter_concurrency=${writer_concurrency} -Dmysql_table=${tableItem}" # >> ${workspace}/../logs/exportData.log fi done
簡單說說我這個shell腳本的用途,主要是對datax中的job配置文件的動態參數進行控制。另外,根據公司業務的不同需求,這十幾個需要導入mysql的表其中有些表在導入之前和導入之後需要做不同的完善工作,這個通過這shell來控制。對於這個Shell腳本我是花了點時間進行重構的,功能點還是比較清晰、簡潔的。
步驟4:
執行腳本:nohup ./export_hdfs2mysql.sh 20140815 >> ./../idigg_task/logs/export.log & 大功告成。
三、總結
本blog主要介紹了datax框架、對它的部署、與hadoop2.x的兼容性修改和結合我的個人開發案例說了下datax的實際使用。整個Datax的部署和使用過程還是比較方便的,其效率也是相當不錯,而且性能是可控的(通過job配置文件配置讀、寫線程數)。在大多數情況下,datax和sqoop的性能上可以作爲互補,是一個相當不錯的產品。另外,說說Shell。Shell是我個人最喜歡的一種威武工具,它不僅具有天然的操作系統原生優勢,同時它具有強大的粘合作用,可以將各種技術非常完美的粘合在一個項目之中。熟練的掌握Shell的編寫,可以使一個開發者的戰鬥力上升幾個等級,這個是我在實際工作中總結出來的絕對的真理。
轉載請標明出處:http://zengzhaozheng.blog.51cto.com/8219051/1540679