Flink自定義DataSource之MysqlSource

很久沒更新博客了,最近兩週先後準備了兩個比賽,還好,結果都不錯,繼續加油。迴歸正常的Flink學習之路。

在Flink使用中經常需要自定義DataSource,以滿足實際業務需求。Flink Source原生支持包括Kafka、RabbitMQ等一些常用的消息隊列組件或者類似ES這樣基於文本索引的高性能非關係型數據庫,而對於寫入關係型數據庫或Flink不支持的組件中,需要藉助RichSourceFunction去實現,但這部分性能是比原生的差些,雖然Flink不建議這麼做,但在大數據處理過程中,由於業務或技術架構的複雜性,有些特定的場景還是需要這樣做,本篇文章從實現一個自定義source—MysqlSource來認識RichSourceFunction,讀取mysql中flinktest數據庫中的數據。表很簡單,就省略建表的過程,測試數據如下:

一、maven依賴

我們需要引入以下幾個依賴:Flink基礎包、flink-java包、flink-Streaming包、mysql-jdbc包,因爲我們本次使用的程序語言是java,因此不需要導入scala相關的包進來。

<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>1.7.2</version>
            <!--編譯時使用,運行時並不使用,所以本地測試需要註釋掉-->
            <!--<scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.11</artifactId>
            <version>1.7.2</version>
            <!--<scope>provided</scope>-->
        </dependency>
    </dependencies>

二、Student實體類

用javabean的形式來保存數據庫中的字段,這樣的做的好處是: 繼承時直接寫RichSourceFunction<Student>即可,不定義實體類,我們需要用Tuple來代替,比我我們這裏有四個字段,繼承時寫法如下:RichSourceFunction<Tuple4<Integer, String, String,String>>。如果字段更多,處理更麻煩,因此推薦封裝成實體類。

package com.xpu.flinkjdbc;

/**
 * 實體類 用於存儲數據庫中的數據 javabean
 * create by xiax.xpu on @Date 2019/3/21 20:27
 */
public class Student {
   private int stuid;
   private String stuname;
   private String stuaddr;
   private String stusex;
    public Student(int stuid, String stuname, String stuaddr, String stusex) {
        this.stuid = stuid;
        this.stuname = stuname;
        this.stuaddr = stuaddr;
        this.stusex = stusex;
    }
    public int getStuid() {
        return stuid;
    }
    public void setStuid(int stuid) {
        this.stuid = stuid;
    }
    public String getStuname() {
        return stuname;
    }
    public void setStuname(String stuname) {
        this.stuname = stuname;
    }
    public String getStuaddr() {
        return stuaddr;
    }
    public void setStuaddr(String stuaddr) {
        this.stuaddr = stuaddr;
    }
    public String getStusex() {
        return stusex;
    }
    public void setStusex(String stusex) {
        this.stusex = stusex;
    }
    @Override
    public String toString() {
        return "Student{" +
                "stuid=" + stuid +
                ", stuname='" + stuname + '\'' +
                ", stuaddr='" + stuaddr + '\'' +
                ", stusex='" + stusex + '\'' +
                '}';
    }
}

三、JDBC連通測試

在編寫Flink程序連接mysql時,最好先編寫一個測試類,看能否使用JDBC連通Mysql。測試類:

package com.xpu.flinkjdbc;

import com.sun.xml.internal.bind.v2.bytecode.ClassTailor;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 此類主要檢測jdbc 連接是否成功
 * create by xiax.xpu on @Date 2019/3/21 20:32
 */
public class JdbcTest {
    public static void main(String[] args) throws Exception{
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://192.168.1.12:3306/flinktest";
        String username  = "sqoopuser";
        String password = "sqoopuser";
        Connection connection = null;
        Statement statement = null;

        try {
            //加載驅動
            Class.forName(driver);
            //創建連接
            connection = DriverManager.getConnection(url,username,password);
            //獲取執行語句
            statement = connection.createStatement();
            //執行查詢,獲得結果集
            ResultSet resultSet = statement.executeQuery("select stuid,stuname,stuaddr,stusex from student");
            //處理結果集
            while (resultSet.next()){
                Student student = new Student(
                        resultSet.getInt("stuid"),
                        resultSet.getString("stuname").trim(),
                        resultSet.getString("stuaddr").trim(),
                        resultSet.getString("stusex").trim());
                System.out.println(student);

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉連接 釋放資源
            if (connection != null){
                connection.close();
            }
            if (statement != null){
                connection.close();
            }
        }

    }
}

測試結果如下所示:

 

四、StudentSourceFromMysql 自定義

新建 StudentSourceFromMysql類,該類繼承RichSourceFunction,這裏需要指定類型,否則代碼運行時會報類型不匹配。實現裏面的open、run、close、cancel方法,方法說明均在代碼裏:

package com.xpu.flinkjdbc;

import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * create by xiax.xpu on @Date 2019/3/21 21:26
 */
public class StudentSourceFromMysql extends RichSourceFunction<Student> {

    private PreparedStatement ps=null;
    private Connection connection=null;
    String driver = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://192.168.1.12:3306/flinktest";
    String username  = "sqoopuser";
    String password = "sqoopuser";

    /**
     * open()方法中建立攔截,這樣不用每次invoke的時候都需要建立連接和釋放連接
     * org.apache.flink.api.common.functions.AbstractRichFunction#open
     */
    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        connection = getConnection();
        String sql = "select * from student;";
        //獲取執行語句
        ps = connection.prepareStatement(sql);
    }

    /**
     * DataStream 調用一次 run()方法執行查詢並處理結果集
     * org.apache.flink.streaming.api.functions.source.SourceFunction#run
     */
    @Override
    public void run(SourceContext<Student> ctx) throws Exception {
        ResultSet resultSet = ps.executeQuery();
        while (resultSet.next()){
            Student student = new Student(
                    resultSet.getInt("stuid"),
                    resultSet.getString("stuname").trim(),
                    resultSet.getString("stuaddr").trim(),
                    resultSet.getString("stusex").trim());
            ctx.collect(student);//發送結果
        }
    }

    /**
     * 取消一個job時
     * org.apache.flink.streaming.api.functions.source.SourceFunction#cancel()
     */
    @Override
    public void cancel() {
    }

    /**
     * 關閉數據庫連接
     * org.apache.flink.api.common.functions.AbstractRichFunction#close()
     */
    @Override
    public void close() throws Exception {
        super.close();
        if(connection != null){
            connection.close();
        }
        if (ps != null){
            ps.close();
        }
    }

    //獲取mysql連接配置
    public Connection getConnection(){
        try {
            //加載驅動
            Class.forName(driver);
            //創建連接
            connection = DriverManager.getConnection(url,username,password);
        } catch (Exception e) {
            System.out.println("********mysql get connection occur exception, msg = "+e.getMessage());
            e.printStackTrace();
        }
        return  connection;
    }
}

五、Flink程序入口代碼

主要是獲取運行環境,綁定數據源並設置Sink的方式,本例中是直接調用Print()打印在控制檯上。

package com.xpu.flinkjdbc;

import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * create by xiax.xpu on @Date 2019/4/10 16:05
 */
public class FlinkSubmitter {
    public static void main(String[] args) throws Exception{
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Student> record = env.addSource(new StudentSourceFromMysql());
        //對於record可以添加一些處理邏輯
        record.print().setParallelism(2);
        env.execute("Flink Mysql Source");
    }
}

運行FlinkSubmitter.mian()結果如下:

我們來分析一些這個結果,print的時候並行度設置爲2 ,因此大家會看見有兩個線程,但是2線程接管了第一次的數據輸入,從而出現了上述的情況,我們再重新執行以下:

這次是線程1接管讀取了第一條數據。

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