hadoop讀寫mysql數據庫

  hadoop技術推出一度曾遭到關係數據庫研究者的挑釁和批評,認爲MapReduce不具有關係數據庫中的結構化數據存儲和處理能力。爲此,hadoop社區和研究人員做了多的努力,在hadoop0.19版支持


MapReduce訪問關係數據庫,如:MySQL、Mongodb、PostgreSQL、Oracle 等幾個數據庫系統。Hadoop 訪問關係數據庫主要通過DBInputFormat類實現的,包的位置在 org.apache.hadoop.mapred.lib.db。


本課程我們以 Mysql爲例來學習 MapReduce讀寫數據。


[讀數據]

        DBInputFormat 在 Hadoop 應用程序中通過數據庫供應商提供的 JDBC接口來與數據庫進行交互,並且可以使用標準的 SQL 來讀取數據庫中的記錄。學習DBInputFormat首先必須知道二個條件。


        第一、在使用 DBInputFormat 之前,必須將要使用的 JDBC 驅動拷貝到分佈式系統各個節點的$HADOOP_HOME/lib/目錄下。


        第二、MapReduce訪問關係數據庫時,大量頻繁的從MapReduce程序中查詢和讀取數據,這大大的增加了數據庫的訪問負載,因此,DBInputFormat接口僅僅適合讀取小數據量的數據,而不適合處理數據倉庫。


提示:處理數據倉庫的方法有:利用數據庫的 Dump 工具將大量待分析的數據輸出爲文本,並上傳到 HDFS 中進行理。


        下面我們來看看 DBInputFormat類的內部結構,DBInputFormat 類中包含以下三個內置類。


        1、protected class DBRecordReader implementsRecordReader< LongWritable, T>:用來從一張數據庫表中讀取一條條元組記錄。


        2、public static class NullDBWritable implements DBWritable,Writable:主要用來實現 DBWritable 接口。DBWritable接口要實現二個函數,第一是write,第二是readFileds,這二個函數都不難理解,一個是寫,一個是讀出所有字段。原型如下:


public void write(PreparedStatement statement) throwsSQLException;

public void readFields(ResultSet result);

        3、protected static class DBInputSplit implements InputSplit:主要用來描述輸入元組集合的範圍,包括 start 和 end 兩個屬性,start 用來表示第一條記錄的索引號,end 表示最後一條記錄的索引號。


        下面對怎樣使用 DBInputFormat 讀取數據庫記錄進行詳細的介紹,具體步驟如下:


        步驟一、配置 JDBC 驅動、數據源和數據庫訪問的用戶名和密碼。代碼如下。


DBConfiguration.configureDB (Job job, StringdriverClass, String dbUrl, String userName, String passwd)

        MySQL 數據庫的 JDBC 的驅動爲“com.mysql.jdbc.Driver”,數據源爲“jdbc:mysql://localhost/testDB”,其中testDB爲訪問的數據庫。useName一般爲“root”,passwd是你數據庫的密碼。


        步驟二、使用 setInput 方法操作 MySQL 中的表,setInput 方法的參數如下。


 DBInputFormat.setInput(Job job, Class< extends DBWritable> inputClass, String tableName, String conditions,String orderBy, String... fieldNames)

        這個方法的參數很容易看懂,inputClass實現DBWritable接口。string tableName表名, conditions表示查詢的條件,orderby表示排序的條件,fieldNames是字段,這相當與把sql語句拆分的結果。當然也可以用sql語句進行重載,代碼如下。


setInput(Job job, Class< extends DBWritable> inputClass, String inputQuery, StringinputCountQuery)。

        步驟三、編寫MapReduce函數,包括Mapper 類、Reducer 類、輸入輸出文件格式等,然後調用job.waitForCompletion(true)。


        我們通過示例程序來看看 MapReduce 是如何讀數據的,假設 MySQL 數據庫中有數據庫 user,假設數據庫中的字段有“uid”,“email”,“name"。


        第一步要實現DBwrite和Writable數據接口。代碼如下:


package com.dajiangtai.hadoop.advance;

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import org.apache.hadoop.io.Writable;

import org.apache.hadoop.mapred.lib.db.DBWritable;

public class UserRecord implements Writable, DBWritable {

int uid;

String email;

String name;

/*

*從數據庫讀取所需要的字段

*

*/

@Override

public void readFields(ResultSet resultSet) throws SQLException {

// TODO Auto-generated method stub

this.uid = resultSet.getInt(1);

this.email = resultSet.getString(2);

this.name = resultSet.getString(3);

}


/*

*向數據庫寫入數據

*

*/

@Override

public void write(PreparedStatement statement) throws SQLException {

// TODO Auto-generated method stub

statement.setInt(1, this.uid);

statement.setString(2, this.email);

statement.setString(3, this.name);

}


/*

*讀取序列化數據

*

*/

@Override

public void readFields(DataInput in) throws IOException {

// TODO Auto-generated method stub

this.uid = in.readInt();

this.email = in.readUTF();

this.name = in.readUTF();

}


/*

*將數據序列化

*

*/

@Override

public void write(DataOutput out) throws IOException {

// TODO Auto-generated method stub

out.writeInt(uid);

out.writeUTF(email);

out.writeUTF(name);

}

public String toString() {

        return new String(this.uid + " " + this.email + " " +this.name);

}

}

        第二步,實現Map和Reduce類


public static class ConnMysqlMapper extends Mapper< LongWritable,UserRecord,Text,Text> {

       public void map(LongWritable key,UserRecord values,Context context) 

                        throws IOException,InterruptedException {

           //從 mysql 數據庫讀取需要的數據字段

                context.write(new Text(values.uid+""), new Text(values.name +" "+values.email));

        }

}

public static class ConnMysqlReducer extends Reducer< Text,Text,Text,Text> {

        public void reduce(Text key,Iterable< Text> values,Context context) 

                        throws IOException,InterruptedException {

           //將數據輸出到HDFS中

                for(Iterator< Text> itr = values.iterator();itr.hasNext();) {

                        context.write(key, itr.next());

                }

        }

}

        第三步:主函數的實現


/**

 * @function MapReduce 連接mysql數據庫 讀取數據

 * @author 小講

 *

 */

public class ConnMysql {

public static void main(String[] args) throws Exception {

Configuration conf = new Configuration();

//輸出路徑

         Path output = new Path("hdfs://single.hadoop.dajiangtai.com:9000/advance/mysql/out");

         

         FileSystem fs = FileSystem.get(URI.create(output.toString()), conf);

         if (fs.exists(output)) {

                 fs.delete(output);

         }

         

         //mysql的jdbc驅動

         DistributedCache.addFileToClassPath(new Path("hdfs://single.hadoop.dajiangtai.com:9000/advance/jar/mysql-connector-java-5.1.14.jar"), conf);  

         //設置mysql配置信息   4個參數分別爲: Configuration對象、mysql數據庫地址、用戶名、密碼

         DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://hadoop.dajiangtai.com:3306/djtdb_www", "username", "password");  

         

         Job job = new Job(conf,"test mysql connection");//新建一個任務

         job.setJarByClass(ConnMysql.class);//主類

         

         job.setMapperClass(ConnMysqlMapper.class);//Mapper

         job.setReducerClass(ConnMysqlReducer.class);//Reducer

         

         job.setOutputKeyClass(Text.class);

         job.setOutputValueClass(Text.class);

         

         job.setInputFormatClass(DBInputFormat.class);//從數據庫中讀取數據

         FileOutputFormat.setOutputPath(job, output);

         

         //列名

         String[] fields = { "uid", "email","name" }; 

         //六個參數分別爲:

         //1.Job;2.Class< extends DBWritable> 3.表名;4.where條件 5.order by語句;6.列名

DBInputFormat.setInput(job, UserRecord.class,"user", null, null, fields);           

         System.exit(job.waitForCompletion(true) ? 0 : 1);

         }

 }

        第四步:運行命令如下。


[hadoop@single-hadoop-dajiangtai-com djt]$ hadoop jar ConnMysql.jar  com.dajiangtai.hadoop.advance.ConnMysql

        第五步:查看結果如下所示。


[hadoop@single-hadoop-dajiangtai-com djt]$ hadoop fs -text /advance/mysql/out/

54 無情 [email protected]

55 冷血 [email protected]

提示 MapReduce 操作 MySQL 數據庫在實際工作中比較常用,例如把 MySQL 中的數據遷移到 HDFS 中, 當然還有個很好的方法把 MySQL 或 Oracle 中的數據遷移到 HDFS 中,這個工具是 Pig,如果有這


方面的需求建議使用 Pig。



[寫數據]


        數據處理結果的數據量一般不會太大,可能適合hadoop直接寫入數據庫中。hadoop提供了數據庫接口,把 MapReduce 的結果直接輸出到 MySQL、Oracle 等數據庫。 主要的類如下所示。


        1、DBOutFormat: 提供數據庫寫入接口。


        2、DBRecordWriter:提供向數據庫中寫入的數據記錄的接口。


        3、DBConfiguration:提供數據庫配置和創建鏈接的接口。


        下面我們通過示例來看看 MapReduce 如何向數據庫寫數據,假設 MySQL 數據庫中有數據庫 test,假設數據庫中的字段有“uid”,“email”,“name"。


        第一步同上定義UserRecord實現DBwrite和Writable數據接口。代碼不再贅敘。


        第二步,實現Map和Reduce類,代碼如下所示。


public static class ConnMysqlMapper extends Mapper< LongWritable,Text,Text,Text>

   {  

        public void map(LongWritable key,Text value,Context context)throws IOException,InterruptedException

        {  

//讀取 hdfs 中的數據

        String email = value.toString().split("\\s")[0];

        String name = value.toString().split("\\s")[1];

            context.write(new Text(email),new Text(name));  

        }  

}  

public static class ConnMysqlReducer extends Reducer< Text,Text,UserRecord,UserRecord>

    {  

        public void reduce(Text key,Iterable< Text> values,Context context)throws IOException,InterruptedException

        {  

        //接收到的key value對即爲要輸入數據庫的字段,所以在reduce中:

        //wirte的第一個參數,類型是自定義類型UserRecord,利用key和value將其組合成UserRecord,然後等待寫入數據庫

        //wirte的第二個參數,wirte的第一個參數已經涵蓋了要輸出的類型,所以第二個類型沒有用,設爲null

        for(Iterator< Text> itr = values.iterator();itr.hasNext();)

                 {  

                     context.write(new UserRecord(key.toString(),itr.next().toString()),null);

                 }  

        }  

}

        

        第三步:主函數的實現,代碼如下所示。


/**

 * 將mapreduce的結果數據寫入mysql中

 * @author 小講

 */

public class WriteDataToMysql {

public static void main(String args[]) throws IOException, InterruptedException, ClassNotFoundException

    {

        Configuration conf = new Configuration();

//配置 JDBC 驅動、數據源和數據庫訪問的用戶名和密碼

        DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver","jdbc:mysql://hadoop.dajiangtai.com:3306/djtdb_www","username", "password");    

        Job job = new Job(conf,"test mysql connection");//新建一個任務 

        job.setJarByClass(WriteDataToMysql.class);//主類  

          

        job.setMapperClass(ConnMysqlMapper.class); //Mapper 

        job.setReducerClass(ConnMysqlReducer.class); //Reducer 

          

        job.setOutputKeyClass(Text.class);  

        job.setOutputValueClass(Text.class);

        

        job.setInputFormatClass(TextInputFormat.class);  

        job.setOutputFormatClass(DBOutputFormat.class);//向數據庫寫數據

        //輸入路徑

        FileInputFormat.addInputPath(job, new Path("hdfs://single.hadoop.dajiangtai.com:9000/advance/mysql/data/data.txt"));

        //設置輸出到數據庫    表名:test  字段:uid、email、name

        DBOutputFormat.setOutput(job, "test", "uid","email","name");

        System.exit(job.waitForCompletion(true) ? 0 : 1);

    }

}        

        

        第四步:運行命令如下。


[hadoop@single-hadoop-dajiangtai-com djt]$ hadoop jar WriteDataToMysql.jar  com.dajiangtai.hadoop.advance.WriteDataToMysql

        第五步:查看 MySQL 數據庫記錄。


1[email protected]        yangjun

2[email protected]  yangjun 

3[email protected]          binquan


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