Datax與hadoop2.x兼容部署與實際項目應用工作記錄分享

一、概述

    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架構圖:

wKiom1PtpMqgr7e2AAGmnn3LYHc088.jpg

各個組件的作用:

  • 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爲例說明:

wKiom1PtqoqDx-HSAAJAkufP5EQ380.jpg

進入到plugins目錄找到hdfsreader,將hadoop-0.19.2-core.jar刪除,將本地庫替換爲$HAOOP_HOME2.x/lib/native/libhadoop.so。同時添加Hadoop2.x的依賴包,如下圖:

wKiom1Ptq5DAmRCLAAMw-fhcS7w743.jpg

另外,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路徑:

wKioL1PtrxOBC2nQAAQmgVRCwRk456.jpg

下面以t_dp_datax_engine.spec爲例子:

wKioL1Ptr1TzudZgAAHquH2uYQQ529.jpg

上面紅色方框的地方是指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後如下圖所示

 wKiom1Ptr57CQUYbAACvCg12hUI694.jpg

Rpm製作完成後,即可分發、安裝,例如使用

rpm -ivh  t_dp_datax_engine.rpm

即可安裝DataX engine 包,需要注意的是engine的rpm地址源自於上圖的截圖中信息。

如下圖:

 wKiom1Ptr3OwEWI7AACu95LGP0k854.jpg

安裝完成後,在/home/taobao/datax/目錄下會存在如下文件:

wKiom1PtsGLBAbt1AAIZeoIrKOM474.jpg


其他的插件按照這種方式按照好就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:

wKiom1PttJzDrEFhAADPYH21Bp8574.jpg

接着選擇export的目標源,這個我們選擇0:

wKioL1PttffjMsX8AACscgOOhhg820.jpg

步驟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 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章