很久沒更新博客了,最近兩週先後準備了兩個比賽,還好,結果都不錯,繼續加油。迴歸正常的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接管讀取了第一條數據。