Flink的operator chain引發的問題

今天早上在羣裏有人問了這樣一個問題,我當時只看了截圖沒看他的代碼,然後我倆在那聊了半天,最後發現不在一個頻道,後面我仔細看了一下,他的代碼明白了他的邏輯,我先簡單描述一下場景,他在Flink流開始的時候直接把原數據分別sink到了es和gp庫,然後把處理過的流拿了兩個測流輸出,最後分別把這兩個測流sink到了es和gp,相當於他一共有4個sink,但是他在Flink的UI上面看到的DAG圖是這樣的,如下圖所示

 他就很鬱悶,不應該是4個sink嗎,怎麼只顯示了2個,而且他是先做了2個sink,然後處理完,最後又做了兩個sink,他想要的效果是這樣的,如下圖所示

跟DAG的圖顯示完全不一樣,其實並不是DAG顯示錯了,也不是他的代碼有問題,是因爲他不熟悉Flink的operator chain,Flink默認是開啓operator chain的,他會將多個operator,串在一起作爲一個operator chain來執行,這樣可以提高程序的性能,那怎麼才能讓DAG顯示成他想要的效果呢,其實很簡單,有兩種方法,第一在最後一個算子的地方(sink前面)改變算子的併發(和前面的併發不一樣),第二是最後一個算的地方,調用disableChaining方法就可以了,下面看一個demo

package flink

import java.sql
import java.sql.Connection
import java.util.Properties
import org.apache.commons.dbcp2.BasicDataSource
import org.apache.flink.api.common.functions.RuntimeContext
import org.apache.flink.streaming.api.scala._
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.collector.selector.OutputSelector
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.connectors.elasticsearch.{ElasticsearchSinkFunction, RequestIndexer}
import org.apache.flink.streaming.connectors.elasticsearch6.ElasticsearchSink
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011
import org.apache.http.HttpHost
import org.elasticsearch.client.Requests
import org.elasticsearch.common.xcontent.XContentType

/**
  * Flink消費多個topic的數據,sink到不同的表
  */
object MoreTopic {
  private val broker = "***"
  private val group_id = "***"
  private val topic = "***"

  def main(args: Array[String]): Unit = {
    //獲取流的環境;
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(8)
    val properties = new Properties()
    properties.setProperty("bootstrap.servers", broker)
    properties.setProperty("group.id", group_id)
    // 設置動態監測topic和paritition的變化
    properties.setProperty("flink.partition-discovery.interval-millis","1000")
    // 用正則匹配符合條件的多個topic
    val consumer = new FlinkKafkaConsumer011[String](java.util.regex.Pattern.compile("jason-test-[0-9]"), new SimpleStringSchema, properties)
    // 設置從最新的處開始消費
    consumer.setStartFromLatest()
    val datastream = env.addSource(consumer)
      .filter(_.nonEmpty)
      .flatMap(_.split(","))
      .map(_.trim.toString)
    datastream.map((_,1)).disableChaining()
      .addSink(new MySQLSink_Topic("a"))
      .name("mysql_sink_before_a")
    datastream.map((_,1)).disableChaining()
      .addSink(new MySQLSink_Topic("b"))
      .name("mysql_sink_before_b")
    // 拆分流,把流拆分成一個包含hello,一個包含jason的流
    val split_ds = datastream
      .rebalance
      .split(new OutputSelector[String]{
        override def select(value: String) = {
          val list = new java.util.ArrayList[String]()
          if(value.contains("hello")){
            list.add("h")
          }else{
            list.add("j")
          }
          list
        }
      })
    // 如果想要後面的4個sink在DAG圖顯示並行,需要在最後一個算子上禁用operator chain,或者設置一個和上面不一樣的並行度
    val h_ds = split_ds.select("h").map((_,1)).setParallelism(1)
    val j_ds = split_ds.select("j").map((_,1)).setParallelism(1)

    h_ds.addSink(new MySQLSink_Topic("a")).name("h_ds_mysql_sink")
    j_ds.addSink(new MySQLSink_Topic("b")).name("j_ds_mysql_sink")

    val httpHosts  = new java.util.ArrayList[HttpHost]()
    // http:port 9200 tcp:port 9300
    httpHosts.add(new HttpHost("***", 0, "http"))
    httpHosts.add(new HttpHost("***", 0, "http"))
    httpHosts.add(new HttpHost("***", 0, "http"))
    val elasticsearchSink = new ElasticsearchSinkFunction[(String,Int)] {
      override def process(element: (String,Int), ctx: RuntimeContext, indexer: RequestIndexer) {
        val json = new java.util.HashMap[String, String]()
        json.put("name", element._1)
        json.put("count", element._2.toString)
        println("要寫入es的內容: " + element)
        val r = Requests.indexRequest.index("**").`type`("**").source(json,XContentType.JSON)
        indexer.add(r)
      }
    }

    val esSinkBuilder = new ElasticsearchSink.Builder[(String,Int)](httpHosts, elasticsearchSink)
    // 設置刷新前緩衝的最大算子操作量
    esSinkBuilder.setBulkFlushMaxActions(1)

    h_ds.addSink(esSinkBuilder.build()).name("h_ds_es-sink")
    j_ds.addSink(esSinkBuilder.build()).name("j_ds_es-sink")

    env.execute("Multiple Topic Multiple Sink")
  }
}

/**
  * 把結果保存到mysql裏面
  */
class MySQLSink_Topic(table: String) extends RichSinkFunction[(String,Int)] with Serializable {
  var connection: sql.Connection = _
  var ps: sql.PreparedStatement = _
  var statement: java.sql.Statement = _
  val username = "***"
  val password = "***"
  val drivername = "***"
  val url = "***"
  private var conn: Connection  = _
  /**
    * 打開mysql的連接
    * @param parameters
    */
  override def open(parameters: Configuration): Unit = {
    conn = getConn(new BasicDataSource)
    val sql = "insert into "+ table +"(name,count) values(?,?)"
    println(sql)
    ps = conn.prepareStatement(sql)
  }

  /**
    * 處理數據後寫入mysql
    * @param value
    */
  override def invoke(row: (String,Int)): Unit = {
    ps.setString(1,row._1)
    ps.setLong(2,row._2)
    ps.execute()
  }

  /**
    * 關閉mysql的連接
    */
  override def close(): Unit = {
    if (ps != null) {
      ps.close()
    }
    if (connection != null) {
      connection.close()
    }
  }

  /**
    * 創建mysql的連接池
    * @param ds
    * @return
    */
  def getConn(ds: BasicDataSource): Connection = {
    ds.setDriverClassName("com.mysql.jdbc.Driver");
    ds.setUrl("jdbc:mysql://master:3306/test");
    ds.setUsername("mysql");
    ds.setPassword("12345678");
    ds.setInitialSize(10);
    ds.setMaxTotal(15);
    ds.setMinIdle(10);
    var con: Connection = null
    con = ds.getConnection
    con
  }
}

我直接拿之前的代碼改了一下,我的兩個sink是es和mysql,後面的邏輯和他稍微不同,我這裏用的是分流,他用的是測流輸出,當然我所有的數據都是sink到同一個表的,所以前面加了map處理數據的結構,所以我一共有6個sink,看下面的DAG圖

這裏把最下面的map和後面的兩個sink去掉就和他要的效果一樣了,到這大家應該明白怎麼回事了.

這裏再說兩個小細節方面的東西,先看一張下面的圖片

大家可能對這裏有疑問爲什麼我只有8個slot,卻能運行74個task,這裏的74代表的是task數,就是你所有的operator的併發的總數,也就是上面DAG裏面的8*9+1+1

還有一個小問題是記錄數的問題,如下圖所示

這個地方很多同學也有疑惑,說爲什麼我明明有數據,這裏的Records sent
顯示爲0呢,是因爲print或者是sink的sent就是0,只有received,因爲他們沒有向下一個算子發送數據.

如果有寫的不對的地方,歡迎大家指正,如果有什麼疑問,可以加QQ羣:340297350,更多的Flink和spark的乾貨可以加入下面的星球

 

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