Spark跟Flink的JDBC,都不靠譜

大資料技術前線發表於2023-11-13

來源:安瑞哥是碼農

別誤會,這個不靠譜,說的是不論Spark還是Flink,都不能透過JDBC的方式,支援真正意義上的流式讀取,而不是它不可用。


至少,從目前這兩者的官方文件說明,或者透過我的親身實踐來看是這樣。


那麼下面,就來詳細聊聊,JDBC這種雖然具有普適性的資料庫連線方式,在流式讀取(或者說計算)中會存在哪些短板。



0. 資料來源的讀取分類


我們知道,隨著企業對資料處理的要求越來越高,這就直接導致我們的資料處理系統,對資料來源的讀取方式有著更加多變的要求。


對資料來源的讀取方式和頻率,從業務端的使用需求場景來看,大致可以分為兩大類:


第1種:為一次性的,即一次讀取完目標系統(比如資料庫)的所有資料,我們稱之為「批處理;


第2種:連續性的,在第1種的基礎上,依然監控著資料來源端的變化,繼續讀取後續新增、變化的資料,我們稱之為「流處理


其中第1種,是我們對資料來源讀取的傳統要求,主流的計算引擎Spark和Flink都能滿足。


但對於第2種,雖然無論是Spark還是Flink這兩款計算引擎本身,都支援流式計算這個特性,但是這個支援其實有個重要的前提,那就是得資料來源端,以及跟資料來源端對應的對接方式配合才可以



1. Spark的JDBC


之前我吹過牛逼,說但凡你能叫出名字的儲存系統,或者資料庫,Spark都有與之對應的介面,從而讀取到其中的資料,或者將計算後的結果儲存到其中。


誠然,Spark確實能做到這一點,但是,當我們想要用流的方式去讀取一些特定資料庫的資料來源時,它卻顯得有些力不從心了。


比如,我想讓它以流的方式去讀取mysql中的資料,咋整?


能想到的就是用它的structured streaming框架,來嘗試讀取mysql,但是呢,開啟官網一看它支援的資料來源(最新的),不免有點失落:


Spark跟Flink的JDBC,都不靠譜

spark structured streaming支援的內建資料來源

也就是說,官方明確支援可以用流的方式讀取的資料來源中,是沒有mysql的,也沒有提到JDBC。


但是呢,之前的多次實踐經驗告訴我,有時候官方的話也不能全信,我們最好還是親自試驗一把,萬一可以呢,對吧。


根據以往的經驗,我寫出瞭如下的核心程式碼(記得提前在pom檔案中引入對應的mysql-connector包):


Spark跟Flink的JDBC,都不靠譜


看起來好像是那麼回事,但是一執行起來你會發現:


Spark跟Flink的JDBC,都不靠譜


我靠,果然不行,官網誠不欺我。


但我知道,Spark肯定是可以透過JDBC的方式讀取到mysql資料來源的。


於是,把核心程式碼改成這樣,就能跑通了:


Spark跟Flink的JDBC,都不靠譜

只是這樣一來,就違背了我的初衷,這個邏輯就由原本我想要的「流計算」給硬生生改造成了「批處理」,也就說,這個修改之後的程式碼,它就不再是 Spark structured streaming 而是普通的 Spark。


所以說,Spark官方做不到(至少目前為止)直接用JDBC,以流的方式讀取資料來源。


在GitHub上看到一個開源專案,透過對官方原生支援的jdbc方式改造之後,說可以支援用Spark structured streaming來增量讀取mysql資料來源,我暫時沒有去驗證,有興趣的同學可以去看看。


地址為:



2. Flink的JDBC


開啟Flink的官網,在Flink connector一欄中赫然就出現了JDBC的身影(也沒有mysql):


Spark跟Flink的JDBC,都不靠譜

那既然這樣,我們就來試試。


首先需要配置開發環境,跟Spark不一樣的是,Flink想要讀取mysql的資料來源,那需要引入flink特有的jdbc connector(非傳統的mysql-connector)。


Spark跟Flink的JDBC,都不靠譜

注意這個版本的選擇,可能跟官網上描述的不一樣,最新官方文件的 version 由 connector 版本+Flink版本兩部分組成,而我這個版本稍微舊一點。


然後就是程式碼部分,如下(跟上面的Spark一樣,這裡只演示讀取mysql資料,然後列印出來):


package com.anryg.mysql.jdbc

import java.time.Duration

import org.apache.flink.contrib.streaming.state.EmbeddedRocksDBStateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment

/**
  * @DESC: 用JDBC方式讀取mysql資料來源
  * @Auther: Anryg
  * @Date: 2023/11/8 10:49
  */

object FromMysql2Print {

    def main(args: Array[String]): Unit = {
        val env = StreamExecutionEnvironment.getExecutionEnvironment

        env.enableCheckpointing(10000L)

        env.setStateBackend(new EmbeddedRocksDBStateBackend(true)) //新的設定state backend的方式
        env.getCheckpointConfig.setCheckpointStorage("hdfs://192.168.211.106:8020/tmp/flink_checkpoint/FromMysql2Print")
        env.getCheckpointConfig.setExternalizedCheckpointCleanup(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION) //設定checkpoint記錄的保留策略
        env.getCheckpointConfig.setAlignedCheckpointTimeout(Duration.ofMinutes(1L))
        env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)

        val tableEnv = StreamTableEnvironment.create(env)


        /**第一步:讀取mysql資料來源*/
        tableEnv.executeSql(
            """
             Create table data_from_mysql(
             |`client_ip` STRING,
             |`domain` STRING,
             |`time` STRING,
             |`target_ip` STRING,
             |`rcode` int,
             |`query_type` int,
             |`authority_record` STRING,
             |`add_msg` STRING,
             |`dns_ip` STRING,
             |PRIMARY KEY(`client_ip`,`domain`,`time`,`target_ip`,`rcode`,`query_type`) NOT ENFORCED
             |)
             |with(
             |'connector' = 'jdbc',
             |'url' = 'jdbc:mysql://192.168.221.173:3306/test',
             | 'username' = '***',
             | 'password' = '***',
             | 'table-name' = 'test02'                      //確定文字資料來源的分隔符
             |)
            "
"".stripMargin)
        
        /**結果直接列印*/
        tableEnv.executeSql(
            """
              |select * from data_from_mysql limit 100
            "
"".stripMargin).print()

    }

}

整個程式碼內容,跟上篇文章寫的CDC方式讀取mysql非常相似(有興趣可以去看我的上篇文章對比一下)。


但是呢,把它執行起來之後,當程式把目前這張表的資料讀取完,就會戛然而止


Spark跟Flink的JDBC,都不靠譜

也就說,雖然我們用的是Flink流式計算的上下文(StreamExecutionEnvironment),但是,因為程式用的JDBC方式讀取資料來源,所以,它依然只能以批處理的方式執行


在這點上,Flink跟Spark的表現是一毛一樣的。



最後


透過上面的驗證可以確定,不管是Spark還是Flink,想要以JDBC的方式來流式讀取mysql資料來源(或者其他資料庫)是行不通的,至少,直接用官方提供的「正規軍」方式是不行的。


那麼,對於想直接透過計算引擎,去讀取某些資料庫(比如mysql)的增量資料,好像當下的最優解決方案只有Flink CDC了。


當然,JDBC也並不是一無是處,對於一些低版本的資料庫(CDC暫時不支援的),比如mysql5.5及以下版本的歷史資料匯入,它還是能派上用場的。


對吧。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027827/viewspace-2994901/,如需轉載,請註明出處,否則將追究法律責任。

相關文章