Clickhouse系列之Flink消费Kafka实时存储案例

941 阅读3分钟

本文正在参加「技术专题19期 漫谈数据库技术」活动

前言

随着企业的业务的快速发展,我们在处理实际的需求的过程中会发现,有些数据是实时要求查看,有些是可以T+1的需求,因此为了应付这些需求,实时计算和离线计算去处理这些需求,因此对大数据的要求也越来越高,你既要会离线,又要会实时,这就好比你要会前端也要会后端开发一样。既然实时计算这么重要,我们来看看它解决哪些问题呢?

实时数据解决什么问题
  • 指标监控:比如有实时大盘,用来即时反馈业务当日运转的健康度等场景;
  • 实时特征:比如搜索、广告CTR预估等,对算法特征数据新鲜度要求较高的场景;
  • 事件处理:比如一些风控类、运营活动发券等事件驱动型场景;
  • 数据对账:比如 金融的支付业务,支付部门与业务部门各自独立,当业务部门的支付单据与支付部门不一致时,会造成资损,这时数据的实时对账就非常关键。 image.png

既然聊到了实时计算,当下最火的计算框架可以了解下--Apache Flink。 Flink和Spark一样都是基于内存计算,由于它具备高吞吐和低延迟特性,但是它的速度和效率很高,它也是一个分布式的流处理框架,主要对数据流(有界数据和无边界数据)进行处理。Spark streaming虽然也是流式框架,但是它是基于微批处理,这也是大家认为Spark并不是真正的实时流式处理。 这里我简单介绍一下,更多的知识点可以去官网看看

image.png

正文

废话这么多了,那么现在我们来看看整个实时存储案例。

  1. 依赖文件,flink依赖kafka,jbdc,clickhouse
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-jdbc_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
            <!--            <scope>provided</scope>-->
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-csv</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-json</artifactId>
            <version>1.11.1</version>
        </dependency>
        <!-- old planner flink table-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-planner_2.11</artifactId>
            <version>${flink.version}</version>
            <!--            <scope>provided</scope>-->
        </dependency>
        <!--new planner-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-planner-blink_2.11</artifactId>
            <version>${flink.version}</version>
            <!--            <scope>provided</scope>-->
        </dependency>

        <dependency>
            <groupId>com.alibaba.ververica</groupId>
            <artifactId>flink-format-changelog-json</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-json</artifactId>
            <version>${flink.version}</version>
        </dependency>  
         <dependency>
            <groupId>ru.yandex.clickhouse</groupId>
            <artifactId>clickhouse-jdbc</artifactId>
            <version>0.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>

2.创建一个case class

case class UserAll(id:Int,name:String, create_time:Timestamp,etl_time:Timestamp,bid:Int, username:String,password:String)

3、看看flink的环境以及kafka配置等(包括并行度、jdbcurl、kafka属性)

    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val tableEnv = StreamTableEnvironment.create(env)
    env.setParallelism(1)

    val ckJdbcUrl = "jdbc:clickhouse://hadoop101:8123/tutorial"
    val ckUserName = "default"
    val ckPassword = ""
    val batchSize = 5


    val props = new Properties()
    props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop101:9092")
    props.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest")
    props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer].getName)
    props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer].getName)

4、我们按照自己的业务需求,对数据进行过滤操作,这里会看到cdc的一些信息,因为整个数据是通过cdc将mysql传输到kafka的。

  val jsonStream: DataStream[JSONObject] = input.map(data => {JSON.parseObject(data)})
    val inputStream: DataStream[JSONObject] = jsonStream.filter(data => "+I".equals(data.getString("op")))

5、手动实现 interface 的方式来传入相关 JDBC Statement build 函数

class CkSinkBuilderJson extends JdbcStatementBuilder[(Int,String, Timestamp,Timestamp,Int,String,String)] {
  def accept(ps: PreparedStatement, v: (Int,String, Timestamp,Timestamp,Int,String,String)): Unit = {
    ps.setInt(1, v._1)
    ps.setString(2, v._2)
    ps.setTimestamp(3, v._3)
    ps.setTimestamp(4, v._4)
    ps.setInt(5, v._5)
    ps.setString(6, v._6)
    ps.setString(7, v._7)

  }
}

6、现在我们来看看整个写入的一个代码。整个过程我们需要将流式数据转为table数据,然后通过sink function 实现jdbc去实现这个写入。

    val resultDataStream =
      tableEnv.toAppendStream[(Int,String, Timestamp,Timestamp,Int,String,String)](resultTable)

    val insertIntoCkSql =
      """
        |  INSERT INTO user_all (
        |    id, name,create_time,etl_time,bid,username,password
        |  ) VALUES (
        |    ?,?,?,?,?,?,?
        |  )
      """.stripMargin

    resultDataStream.addSink(
      JdbcSink.sink[(Int,String, Timestamp,Timestamp,Int,String,String)](
        insertIntoCkSql,
        new CkSinkBuilderJson,
        new JdbcExecutionOptions
          .Builder()
          .withMaxRetries(5)
          .withBatchIntervalMs(50)
          .withBatchSize(batchSize)
          .build(),
        new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
          .withDriverName("ru.yandex.clickhouse.ClickHouseDriver")
          .withUrl(ckJdbcUrl)
          .withUsername(ckUserName)
          .withPassword(ckPassword)
          .build()
      )
    )
    env.execute("ck测试")
  }
}

好了,这个代码案例over了,看着简单,但是如果你去使用都会存在一些问题,这个都是正常吧,大数据没有那么好学,只要坚持其实你会发现就那么回事。

总结

本节主要讲解的是Flink消费Kafka数据存储到Clickhouse数据库的一个实战案例。对于实时需求,一旦你数据存储好了,那么就可以利用Clickhouse的优势去实现实时处理。