SparkStreaming動態讀取配置文件

SparkStreaming動態讀取配置文件

標籤: SparkStreaming HDFS 配置文件 MySql


需求

  • 要實現SparkStreaming在流處理過程中能動態的獲取到配置文件的改變
  • 並且能在不重啓應用的情況下更新配置
  • 配置文件大概一個月改動一次,所以不能太耗性能

爲什麼需要動態讀取配置文件?

在之前的項目中一直使用的讀配置文件的模式是在應用啓動階段一次性讀取配置文件並獲取到其中的全部配置內容。並且,在程序運行過程中這些配置不能被改變,如果需要改變,則需要重新打包發佈應用。

在代碼測試階段這種方式會很麻煩也很費時,所以需要一種能讓應用動態更新配置的方法。

Spark程序的運行過程

1、Spark運行的基本流程

  1. 構建Spark Application的運行環境(啓動SparkContext),SparkContext向資源管理器(可以是Standalone、Mesos或YARN)註冊並申請運行Executor資源;
  2. 資源管理器分配Executor資源並啓動StandaloneExecutorBackend,Executor運行情況將隨着心跳發送到資源管理器上;
  3. SparkContext構建成DAG圖,將DAG圖分解成Stage,並把Taskset發送給Task Scheduler。Executor向SparkContext申請Task,Task Scheduler將Task發放給Executor運行同時SparkContext將應用程序代碼發放給Executor。
  4. Task在Executor上運行,運行完畢釋放所有資源。

Spark運行流程

2、一個Spark程序的執行過程

一個用戶編寫的提交給Spark集羣執行的application包含兩個部分:

  • 驅動: Driver與Master、Worker協作完成application進程的啓動、DAG劃分、計算任務封裝、計算任務分發到各個計算節點(Worker)、計算資源的分配等。
  • 計算邏輯:當計算任務在Worker執行時,執行計算邏輯完成application的計算任務。

那麼哪些作爲“驅動代碼”在Driver進程中執行,哪些“任務邏輯代碼”被包裝到任務中,然後分發到計算節點進行計算?

  • Spark application首先在Driver上開始運行main函數。執行過程中,計算邏輯的開始是從讀取數據源(比如HDFS中的文件)創建RDD開始,RDD分爲transform和action兩種操作,transform使用的是懶執行,而action操作將會觸發Job的提交。
  • Application在Driver上執行,遇到RDD的action動作後,開始提交作業,當作業執行完成後,後面的作業陸續提交。
  • action操作會進行回溯,把懶執行的操作一起打包發送到各個計算節點。簡單來說,發送到計算節點的對RDD的操作就是計算邏輯,其餘的都在Driver中執行。

思考

瞭解了Spark的執行流程之後,不難發現,儘管Spark的開發者很努力的讓Spark編程模式儘可能的靠近普通的順序執行的編程模式。但是作爲一個分佈式執行過程,還是跟普通編程模式有很大區別,不注意的話很容易踩到坑。

我也想過了許多解決方案,但是大部分都卡在了這個分佈式的坑上邊。

比如:

  • 一開始我考慮過是不是把讀取配置文件的代碼寫到SparkContext初始化的代碼之後,並以文件流的形式讀取配置文件想要實現讓Streaming每個批次都讀。瞭解了上面的運行過程之後就會發現這是行不通的。
  • 之後又思考能否利用檢查點機制在程序從檢查點讀取數據時加載配置文件。但是有一個問題,程序發生改變之後檢查點的數據會被丟棄。
  • 然後又調研了市面上存在且常用的幾個第三方配置管理平臺,比如百度disconf,奇虎qconf,淘寶Diamond。調查結果則是現有的這些配置管理平臺都是針對web項目做配置管理,不能很好的支持大數據的這種分佈式架構。

在這些方向都走不通之後,我又重新回到Spark本身的執行流程上思考。Spark的action操作會發布到各個計算節點進行執行,如果把讀取配置文件的操作寫在action操作裏帶到各個節點進行執行,應該可以實現讓每個節點都讀取到配置文件,且可以實時改變。測試的結果也證明了這種方式是可行的。

詳細設計

有了思路接下來就是代碼測試。上面的三種失敗方案也都進行了代碼測試證明是不可行的。

首先是從HDFS中讀取配置文件,在這裏我寫了個工具類:

object HDFSUtil {
  val conf: Configuration = new Configuration
  var fs: FileSystem = null
  var hdfsInStream: FSDataInputStream = null
  val prop = new Properties()

  //獲取文件輸入流
  def getFSDataInputStream(path: String): FSDataInputStream = {
    try {
      fs = FileSystem.get(URI.create(path), conf)
      hdfsInStream = fs.open(new Path(path))
    } catch {
      case e: IOException => {
        e.printStackTrace
      }
    }
    return hdfsInStream
  }

  //讀取配置文件
  def getProperties(path:String,key:String): String = {
    prop.load(this.getFSDataInputStream(path))
    prop.getProperty(key)
  }

然後是SparkStreaming初始化以及處理TCP流數據:

val conf = new SparkConf().setAppName("write data to mysql")
val ssc = new StreamingContext(conf,Seconds(10))
val streamData = ssc.socketTextStream("T002",9999)
val wordCount = streamData.map(line =>(line.split(",")(0),1)).reduceByKey(_+_)
val hottestWord = wordCount.transform(itemRDD => {
  val top3 = itemRDD.map(pair => (pair._2, pair._1))
    .sortByKey(false).map(pair => (pair._2, pair._1)).take(3)
  ssc.sparkContext.makeRDD(top3)
})
hottestWord.foreachRDD( rdd =>{
  rdd.foreachPartition(partitionOfRecords =>{
    val path = "hdfs:///home/wuyue/property/test.properties"
    val MD5Value = HDFSUtil.getHdfsFileMd5(path)
    val sql=HDFSUtil.getProperties(path,"sql")
    HDFSUtil.close

測試讀取的數據是一條sql語句,如果能讀取到就可以把數據正確的存入MySql中,如果讀取不到配置,程序就會報錯。連接數據庫的代碼如下:

val connect = scalaConnectPool.getConnection
    connect.setAutoCommit(false)
    val ps = connect.prepareStatement(sql)
    partitionOfRecords.foreach(record =>{
      val word = record._1
      val count = record._2
      ps.setString(1,word)
      ps.setInt(2,count)
      ps.addBatch()
    })
    ps.executeBatch()
    connect.commit()
    scalaConnectPool.closeConnection(ps,connect)

我寫了一個數據庫連接池方便進行數據庫連接,因爲獲取數據庫的連接並不是很耗性能,sql語句的執行最耗性能,所以出於性能角度考慮,存入MySql的操作我使用的是批處理模式。

經過測試,數據能夠正確的存入數據庫中,並且手動更改配置文件之後程序出錯停止,說明程序能夠讀到配置文件中的變化並進行更新。

設計的不足之處

因爲Spark讀取數據的操作是分佈在各個計算節點執行的,如果使用傳統的文件資源管理器就必須在每個節點機器的目錄下都存放一份配置文件,並且在改動時要同時進行,這是很不方便的。所以在測試中使用HDFS(分佈式文件系統)存儲配置文件。

但是HDFS主要用來做大數據量批量讀寫操作的,對單個文件的隨機讀寫會很慢。

所以我在要讀取配置文件之前增加了一個對文件是否改動進行的判斷,如果配置文件發生變化則重新讀取文件,如果沒有變化則不讀取。

具體實現方式爲獲取文件的MD5值,如果MD5值發生變化說明文件有改動,如果不變說明文件沒有改動。

但即使是這樣,在HDFS上進行隨機讀寫依然很耗性能。因爲是測試階段,主要爲了證明這種方式是可行的,在進一步的測試中可以考慮使用MySql或者Redis替換掉HDFS來存儲配置文件。

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