Flink 從 0 到 1 學習 —— 如何自定義 Data Sink ?

zhisheng發表於2019-09-21

前言

前篇文章 《從0到1學習Flink》—— Data Sink 介紹 介紹了 Flink Data Sink,也介紹了 Flink 自帶的 Sink,那麼如何自定義自己的 Sink 呢?這篇文章將寫一個 demo 教大家將從 Kafka Source 的資料 Sink 到 MySQL 中去。

準備工作

我們先來看下 Flink 從 Kafka topic 中獲取資料的 demo,首先你需要安裝好了 FLink 和 Kafka 。

執行啟動 Flink、Zookepeer、Kafka,

好了,都啟動了!

資料庫建表

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(25) COLLATE utf8_bin DEFAULT NULL,
  `password` varchar(25) COLLATE utf8_bin DEFAULT NULL,
  `age` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;複製程式碼

實體類

Student.java

package com.zhisheng.flink.model;

/**
 * Desc:
 * weixin: zhisheng_tian
 * blog: http://www.54tianzhisheng.cn/
 */
public class Student {
    public int id;
    public String name;
    public String password;
    public int age;

    public Student() {
    }

    public Student(int id, String name, String password, int age) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}複製程式碼

工具類

工具類往 kafka topic student 傳送資料

import com.alibaba.fastjson.JSON;
import com.zhisheng.flink.model.Metric;
import com.zhisheng.flink.model.Student;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 往kafka中寫資料
 * 可以使用這個main函式進行測試一下
 * weixin: zhisheng_tian
 * blog: http://www.54tianzhisheng.cn/
 */
public class KafkaUtils2 {
    public static final String broker_list = "localhost:9092";
    public static final String topic = "student";  //kafka topic 需要和 flink 程式用同一個 topic

    public static void writeToKafka() throws InterruptedException {
        Properties props = new Properties();
        props.put("bootstrap.servers", broker_list);
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer producer = new KafkaProducer<String, String>(props);

        for (int i = 1; i <= 100; i++) {
            Student student = new Student(i, "zhisheng" + i, "password" + i, 18 + i);
            ProducerRecord record = new ProducerRecord<String, String>(topic, null, null, JSON.toJSONString(student));
            producer.send(record);
            System.out.println("傳送資料: " + JSON.toJSONString(student));
        }
        producer.flush();
    }

    public static void main(String[] args) throws InterruptedException {
        writeToKafka();
    }
}複製程式碼

SinkToMySQL

該類就是 Sink Function,繼承了 RichSinkFunction ,然後重寫了裡面的方法。在 invoke 方法中將資料插入到 MySQL 中。

package com.zhisheng.flink.sink;

import com.zhisheng.flink.model.Student;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;

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

/**
 * Desc:
 * weixin: zhisheng_tian
 * blog: http://www.54tianzhisheng.cn/
 */
public class SinkToMySQL extends RichSinkFunction<Student> {
    PreparedStatement ps;
    private Connection connection;

    /**
     * open() 方法中建立連線,這樣不用每次 invoke 的時候都要建立連線和釋放連線
     *
     * @param parameters
     * @throws Exception
     */
    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        connection = getConnection();
        String sql = "insert into Student(id, name, password, age) values(?, ?, ?, ?);";
        ps = this.connection.prepareStatement(sql);
    }

    @Override
    public void close() throws Exception {
        super.close();
        //關閉連線和釋放資源
        if (connection != null) {
            connection.close();
        }
        if (ps != null) {
            ps.close();
        }
    }

    /**
     * 每條資料的插入都要呼叫一次 invoke() 方法
     *
     * @param value
     * @param context
     * @throws Exception
     */
    @Override
    public void invoke(Student value, Context context) throws Exception {
        //組裝資料,執行插入操作
        ps.setInt(1, value.getId());
        ps.setString(2, value.getName());
        ps.setString(3, value.getPassword());
        ps.setInt(4, value.getAge());
        ps.executeUpdate();
    }

    private static Connection getConnection() {
        Connection con = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456");
        } catch (Exception e) {
            System.out.println("-----------mysql get connection has exception , msg = "+ e.getMessage());
        }
        return con;
    }
}複製程式碼

Flink 程式

這裡的 source 是從 kafka 讀取資料的,然後 Flink 從 Kafka 讀取到資料(JSON)後用阿里 fastjson 來解析成 student 物件,然後在 addSink 中使用我們建立的 SinkToMySQL,這樣就可以把資料儲存到 MySQL 了。

package com.zhisheng.flink;

import com.alibaba.fastjson.JSON;
import com.zhisheng.flink.model.Student;
import com.zhisheng.flink.sink.SinkToMySQL;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.PrintSinkFunction;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer011;

import java.util.Properties;

/**
 * Desc:
 * weixin: zhisheng_tian
 * blog: http://www.54tianzhisheng.cn/
 */
public class Main3 {
    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("zookeeper.connect", "localhost:2181");
        props.put("group.id", "metric-group");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("auto.offset.reset", "latest");

        SingleOutputStreamOperator<Student> student = env.addSource(new FlinkKafkaConsumer011<>(
                "student",   //這個 kafka topic 需要和上面的工具類的 topic 一致
                new SimpleStringSchema(),
                props)).setParallelism(1)
                .map(string -> JSON.parseObject(string, Student.class)); //Fastjson 解析字串成 student 物件

        student.addSink(new SinkToMySQL()); //資料 sink 到 mysql

        env.execute("Flink add sink");
    }
}複製程式碼

結果

執行 Flink 程式,然後再執行 KafkaUtils2.java 工具類,這樣就可以了。

如果資料插入成功了,那麼我們檢視下我們的資料庫:

資料庫中已經插入了 100 條我們從 Kafka 傳送的資料了。證明我們的 SinkToMySQL 起作用了。是不是很簡單?

專案結構

怕大家不知道我的專案結構,這裡發個截圖看下:

最後

本文主要利用一個 demo,告訴大家如何自定義 Sink Function,將從 Kafka 的資料 Sink 到 MySQL 中,如果你專案中有其他的資料來源,你也可以換成對應的 Source,也有可能你的 Sink 是到其他的地方或者其他不同的方式,那麼依舊是這個套路:繼承 RichSinkFunction 抽象類,重寫 invoke 方法。

關注我

轉載請務必註明原創地址為:www.54tianzhisheng.cn/2018/10/31/…

微信公眾號:zhisheng

另外我自己整理了些 Flink 的學習資料,目前已經全部放到微信公眾號(zhisheng)了,你可以回覆關鍵字:Flink 即可無條件獲取到。另外也可以加我微信 你可以加我的微信:yuanblog_tzs,探討技術!

更多私密資料請加入知識星球!

Github 程式碼倉庫

github.com/zhisheng17/…

以後這個專案的所有程式碼都將放在這個倉庫裡,包含了自己學習 flink 的一些 demo 和部落格

部落格

1、Flink 從0到1學習 —— Apache Flink 介紹

2、Flink 從0到1學習 —— Mac 上搭建 Flink 1.6.0 環境並構建執行簡單程式入門

3、Flink 從0到1學習 —— Flink 配置檔案詳解

4、Flink 從0到1學習 —— Data Source 介紹

5、Flink 從0到1學習 —— 如何自定義 Data Source ?

6、Flink 從0到1學習 —— Data Sink 介紹

7、Flink 從0到1學習 —— 如何自定義 Data Sink ?

8、Flink 從0到1學習 —— Flink Data transformation(轉換)

9、Flink 從0到1學習 —— 介紹 Flink 中的 Stream Windows

10、Flink 從0到1學習 —— Flink 中的幾種 Time 詳解

11、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 ElasticSearch

12、Flink 從0到1學習 —— Flink 專案如何執行?

13、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 Kafka

14、Flink 從0到1學習 —— Flink JobManager 高可用性配置

15、Flink 從0到1學習 —— Flink parallelism 和 Slot 介紹

16、Flink 從0到1學習 —— Flink 讀取 Kafka 資料批量寫入到 MySQL

17、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 RabbitMQ

18、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 HBase

19、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 HDFS

20、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 Redis

21、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 Cassandra

22、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 Flume

23、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 InfluxDB

24、Flink 從0到1學習 —— Flink 讀取 Kafka 資料寫入到 RocketMQ

25、Flink 從0到1學習 —— 你上傳的 jar 包藏到哪裡去了

26、Flink 從0到1學習 —— 你的 Flink job 日誌跑到哪裡去了

27、阿里巴巴開源的 Blink 實時計算框架真香

28、Flink 從0到1學習 —— Flink 中如何管理配置?

29、Flink 從0到1學習—— Flink 不可以連續 Split(分流)?

30、Flink 從0到1學習—— 分享四本 Flink 國外的書和二十多篇 Paper 論文

31、Flink 架構、原理與部署測試

32、為什麼說流處理即未來?

33、OPPO 資料中臺之基石:基於 Flink SQL 構建實時資料倉儲

34、流計算框架 Flink 與 Storm 的效能對比

35、Flink狀態管理和容錯機制介紹

36、Apache Flink 結合 Kafka 構建端到端的 Exactly-Once 處理

37、360深度實踐:Flink與Storm協議級對比

38、如何基於Flink+TensorFlow打造實時智慧異常檢測平臺?只看這一篇就夠了

39、Apache Flink 1.9 重大特性提前解讀

40、Flink 全網最全資源(視訊、部落格、PPT、入門、實戰、原始碼解析、問答等持續更新)

41、Flink 靈魂兩百問,這誰頂得住?

42、Flink 從0到1學習 —— 如何使用 Side Output 來分流?

43、你公司到底需不需要引入實時計算引擎?

44、一文讓你徹底瞭解大資料實時計算引擎 Flink

原始碼解析

1、Flink 原始碼解析 —— 原始碼編譯執行

2、Flink 原始碼解析 —— 專案結構一覽

3、Flink 原始碼解析—— local 模式啟動流程

4、Flink 原始碼解析 —— standalone session 模式啟動流程

5、Flink 原始碼解析 —— Standalone Session Cluster 啟動流程深度分析之 Job Manager 啟動

6、Flink 原始碼解析 —— Standalone Session Cluster 啟動流程深度分析之 Task Manager 啟動

7、Flink 原始碼解析 —— 分析 Batch WordCount 程式的執行過程

8、Flink 原始碼解析 —— 分析 Streaming WordCount 程式的執行過程

9、Flink 原始碼解析 —— 如何獲取 JobGraph?

10、Flink 原始碼解析 —— 如何獲取 StreamGraph?

11、Flink 原始碼解析 —— Flink JobManager 有什麼作用?

12、Flink 原始碼解析 —— Flink TaskManager 有什麼作用?

13、Flink 原始碼解析 —— JobManager 處理 SubmitJob 的過程

14、Flink 原始碼解析 —— TaskManager 處理 SubmitJob 的過程

15、Flink 原始碼解析 —— 深度解析 Flink Checkpoint 機制

16、Flink 原始碼解析 —— 深度解析 Flink 序列化機制

17、Flink 原始碼解析 —— 深度解析 Flink 是如何管理好記憶體的?

18、Flink Metrics 原始碼解析 —— Flink-metrics-core

19、Flink Metrics 原始碼解析 —— Flink-metrics-datadog

20、Flink Metrics 原始碼解析 —— Flink-metrics-dropwizard

21、Flink Metrics 原始碼解析 —— Flink-metrics-graphite

22、Flink Metrics 原始碼解析 —— Flink-metrics-influxdb

23、Flink Metrics 原始碼解析 —— Flink-metrics-jmx

24、Flink Metrics 原始碼解析 —— Flink-metrics-slf4j

25、Flink Metrics 原始碼解析 —— Flink-metrics-statsd

26、Flink Metrics 原始碼解析 —— Flink-metrics-prometheus

26、Flink Annotations 原始碼解析

27、Flink 原始碼解析 —— 如何獲取 ExecutionGraph ?

28、大資料重磅炸彈——實時計算框架 Flink

29、Flink Checkpoint-輕量級分散式快照

30、Flink Clients 原始碼解析原文出處:zhisheng的部落格,歡迎關注我的公眾號:zhisheng

相關文章