Flume自定義Sink到PostgreSQL

前一篇文章《Flume簡介與安裝》已經介紹了Flume的相關知識,也提到了Flume的各個環節是解耦的,那麼如果實際開發需要自定義開發組件呢?大概流程是怎樣的?

 

下面以輸出到PostgreSQL爲例說明開發新組件的方法。

首先簡單介紹PostgreSQL:

PostgreSQL是一種特性非常齊全的自由軟件的對象-關係型數據庫管理系統(ORDBMS),是以加州大學計算機系開發的POSTGRES,4.2版本爲基礎的對象關係型數據庫管理系統。POSTGRES的許多領先概念只是在比較遲的時候纔出現在商業網站數據庫中。PostgreSQL支持大部分的SQL標準並且提供了很多其他現代特性,如複雜查詢、外鍵、觸發器、視圖、事務完整性、多版本併發控制等。同樣,PostgreSQL也可以用許多方法擴展,例如通過增加新的數據類型、函數、操作符、聚集函數、索引方法、過程語言等。另外,因爲許可證的靈活,任何人都可以以任何目的免費使用、修改和分發PostgreSQL。

 

接下來就是開發過程了:

 

1. 核心代碼

sink的實現需要start()、stop()、process()和configure()這四個方法,分別實現啓動、結束、處理和取參數的功能。

具體代碼如下:

package org.apache.flume.sink.pgsql;
​
import com.google.common.base.Preconditions;
import org.apache.flume.Channel;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.Transaction;
import org.apache.flume.conf.Configurable;
import org.apache.flume.sink.AbstractSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
import java.sql.*;
​
// 類需要繼承AbstractSink這個類和Configurable這個接口
public class PGSQLSink extends AbstractSink implements Configurable {
    // 定義相關對象和變量
    private static final Logger logger = LoggerFactory.getLogger(PGSQLSink.class);
    private static final String DEFAULT_SPLIT_CHARS = ",";
    private Connection connection;
    private Statement statement;
    private String hostname;
    private String port;
    private String url;
    private String user;
    private String password;
    private String tablename;
    private String columnName;
​
// configure方法作用是從配置文件取出相關參數,包括數據庫連接等信息
    @Override
    public void configure(Context context) {
        columnName = context.getString("column_name");
        Preconditions.checkNotNull(columnName, "column_name must be set!!");
        user = context.getString("user");
        Preconditions.checkNotNull(user, "user must be set!!");
        password = context.getString("password");
        Preconditions.checkNotNull(password, "password must be set!!");
        hostname = context.getString("hostname");
        Preconditions.checkNotNull(hostname, "hostname must be set!!");
        port = context.getString("port");
        Preconditions.checkNotNull(port, "port must be set!!");
        url = "jdbc:postgresql://" + hostname + ":" + port + "/postgres";
        Preconditions.checkNotNull(url, "url must be set!!");
        tablename = context.getString("tablename");
        Preconditions.checkNotNull(tablename, "tablename must be set!!");
    }
​
    // 在整個sink結束時執行一遍
    @Override
    public synchronized void stop() {
        super.stop();
    }
​
    // 在整個sink開始時執行一遍
    @Override
    public synchronized void start() {
        super.start();
        try {
            connection = DriverManager.getConnection(url, user, password);
            logger.info("pgsql connected!!!"+connection);
            // 連接URL爲 jdbc:mysql//服務器地址/數據庫名 ,後面的2個參數分別是登陸用戶名和密碼
            statement = connection.createStatement();
            logger.info("statement created****"+statement);
        } catch (SQLException e) {
            // 處理異常
            e.printStackTrace();
        }
    }
​
    // 不斷循環調用
    @Override
    public Status process() throws EventDeliveryException{
​
        Status result = Status.READY;
        Channel channel = getChannel();
        Transaction transaction = channel.getTransaction();
        Event event = null;
​
        try {
            transaction.begin();
            // 從channel取出event
            event = channel.take();
​
            if (event != null) {
                String body = new String(event.getBody(),"UTF-8");
                System.out.println("body: "+body);
                System.out.println("columnName: "+columnName);
                // 拼湊出SQL語句
                String sql = "insert into " + tablename + "(" + columnName + ") values(" + body + ")";
                logger.info(sql);
                // 執行SQL語句
                statement.executeUpdate(sql);
                logger.info("sql execute done.");
            } else {
                // event爲空的處理
                result = Status.BACKOFF;
            }
            transaction.commit();
        } catch (Exception ex) {
            // 處理異常
            transaction.rollback();
            throw new EventDeliveryException("Failed to log event: " + event, ex);
        } finally {
            transaction.close();
        }
    // 返回狀態
    return result;
    }
}

上面的代碼編寫好之後,需要將其打包爲一個jar包(如flume-pgsql-sink-1.6.0.jar)並將其放到Flume安裝目錄下的lib目錄裏;另外,pgsql的驅動jar(如postgresql-42.2.11.jar)要加到該目錄下,否則Flume將無法使用該功能。

 

2.配置文件

爲了配合代碼,需要在Flume安裝目錄下的conf文件夾中新建一個名爲pgsql.conf的文件,將組件的需要用到的參數定義好:

# a1是agent的名稱
# r1是source的名稱
# k1是sink的名稱
# c1是channel的名稱
a1.sources = r1
a1.sinks = k1
a1.channels = c1
​
​
# 配置source
# 這裏是從testData.log文件中採集數據
a1.sources.r1.type = exec
a1.sources.r1.command = tail -f /root/mycode/testData.log
a1.sources.r1.shell = /bin/sh -c
​
​
# 配置sink,包括類名、posgresql的ip地址端口和賬號密碼、表名、字段名
a1.sinks.k1.type = org.apache.flume.sink.pgsql.PGSQLSink
a1.sinks.k1.hostname = 192.168.38.132
a1.sinks.k1.port = 5432
a1.sinks.k1.password = postgres
a1.sinks.k1.user = postgres
a1.sinks.k1.tablename= testcar
a1.sinks.k1.column_name = id,price
​
​
# 配置channel的類型
a1.channels.c1.type = file
​
​
#  關聯好source、sink和channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

 

3. 運行命令:

到bin文件夾下面執行,指定好agent,配置文件和日誌類型:

[root@slave2 bin]# flume-ng agent --name a1 -c conf -f ../conf/pgsql.conf -Dflume.root.logger=INFO,console

新開一個窗口,到/root/mycode/testData.log文件中追加記錄:

echo "1, 200" >> /root/mycode/testData.log
echo "2, 300" >> /root/mycode/testData.log

然後在PostgreSQL的testcar表就可以看到數據導入了,大功告成!


本文轉自微信公衆號:superdiao的果殼

喜歡的朋友可以去關注(關注後有福利哦~),記得先給本文點贊或者點喜歡吶!

微信公衆號:superdiao的果殼

 

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