Flink实战1-数据实时写入Kudu的客户端方式给与业务以Impala实时分析

背景

  • 互联网金融,面对的业务方较多;风控部门的数据分析师,策略分析师,反欺诈分析师等,目前的数据量这些分析师使用Python以及MySQL是无法满足快速高效的分析的;商城、运营部门等的报表看板,定制化用户行为分析等。;目前的自主分析是使用的开源产品Superset做一部分的改造,接入Druid,ES,Impala,分析师们已经全部转到我们的平台,大部分的使用都是基于我们数仓的DWS,但是除此之外实时数据没有完全接入,这是目前的痛点,也是最需要做的;尝试使用HBase做映射使用Impala分析,但是只能按照固定的键高效查询,无法满足更加多元化的定制化分析场景;
  • 之前一直使用Spark Streaming进行实时数据的流转,此次我打算用Flink来做,此篇是一个测试过程,操作也是十分粗糙,比较低效;

摘要

关键字

  • Flink将Maxwell实时数据写入KUDU
  • Flink第一步

设计

  • 完整的设计如下
    1. 使用MySQL作为配置表,每日一次广播
      • 因为实时接入的业务库很多,所以需要进行配置化写入,需要才做添加;
      • 配置库名,表名详细参数等;
    2. Flink对接MaxWell将需要的数据写入KUDU;
    3. Impala集成KUDU于飞天(BI数据分析平台),数分、策略、算法等进行使用;

说明

  • 因为是一个简单的开始Demo,刚开始使用Flink写入,所以有很多待优化的地方,也是需要跟进学习的地方;
  • Demo为使用KUDU客户端,并非标准的Flink-KUDU-sink;
  • Demo只是关于表的创建与写入,过程顺畅之后多表无非是根据不同的配置进行不同的处理了;
  • 之前关于KUDU的熟悉可以看
  • KUDU的API使用,不同客户端操作应对批处理与流处理
    • 这篇帖子是针对与KUDU的一些基础操作,其中涉及到集成Spark,KUDU的原生客户端使用;

实现

  • main

    object FlinkRealTimeMain {
      def main(args: Array[String]): Unit = {
        //环境
        val env = StreamExecutionEnvironment.getExecutionEnvironment
    
        val kuduClientBuilder = new KuduClient.KuduClientBuilder("cdh-01.xxxxx.com:7051").build()
    
        FlinkRealTimeProcessing.processor(env , kuduClientBuilder)
    
        env.execute(this.getClass.getSimpleName)
      }
    }
    
  • traits

    trait FlinkProcessor {
      def processor(env: StreamExecutionEnvironment, kuduClientBuilder: KuduClient)
    }
    
  • Processing

    /**
     * Description:xxxx
    * Copyright (c) ,2020 , jackson
    * This program is protected by copyright laws.
    * Date: 2020年11月26日 * * @author xxx * @version : 1.0 */
    object FlinkRealTimeProcessing extends FlinkProcessor { override def processor(env: StreamExecutionEnvironment, kuduClientBuild: KuduClient): Unit = { /** * 获取基础参数 */ val bootstrapserversnew = Contant.BOOTSTRAP_SERVERS_NEW import org.apache.flink.api.scala._ /** * kudu建表 * 因为之前总结过的KUDU客户端是spark-kudu,同步,异步几种,这个地方使用client就没有在做封装了,不过流程是一模一样的;只是一个简单的Demo */ import scala.collection.JavaConversions._ if (kuduClientBuild.tableExists("kudu_client_test_table_risk_task")) { println("kudu_client_test_table_risk_task已存在!") } else { /** * 创建建表配置项 */ val createTableOptions = new CreateTableOptions createTableOptions.setNumReplicas(1) createTableOptions.addHashPartitions(ListBuffer("id"), 3) /** * 字段结构属性建立 */ val idSchemaBuilder = new ColumnSchema.ColumnSchemaBuilder("id", Type.INT32) val orderNoSchemaBuilder = new ColumnSchema.ColumnSchemaBuilder("order_no", Type.STRING) val applyTimeSchemaBuilder = new ColumnSchema.ColumnSchemaBuilder("apply_time", Type.STRING) val customerNameSchemaBuilder = new ColumnSchema.ColumnSchemaBuilder("customer_name", Type.STRING) /** * 配置结构图 */ val schema = new Schema(ListBuffer( idSchemaBuilder.key(true).build(), orderNoSchemaBuilder.key(false).build(), applyTimeSchemaBuilder.key(false).build(), customerNameSchemaBuilder.key(false).build() )) kuduClientBuild.createTable( "kudu_client_test_table_risk_task", schema, createTableOptions ) } /** * 定义kafka-source得到DataStream */ val topic = "opic" //将kafka中数据反序列化, val valueDeserializer: DeserializationSchema[String] = new SimpleStringSchema() val properties = new Properties() properties.put("bootstrap.servers", bootstrapserversnew) println(Contant.BOOTSTRAP_SERVERS_NEW) val kafkaSinkDStream = env.addSource(new FlinkKafkaConsumer[String](topic, valueDeserializer, properties)) /** * key操作 */ kafkaSinkDStream.keyBy(line => { val kuduClientBuilder = new KuduClient.KuduClientBuilder("host:port").build() /** * 获取kudu连接的session */ val kuduSession = kuduClientBuilder.newSession() /** * 获取kudu表连接 */ val kuduTable = kuduClientBuilder.openTable("kudu_client_test_table_risk_task") val jsonLine = JSON.parseObject(line) val database = jsonLine.get("database").toString val table = jsonLine.get("table").toString val data = jsonLine.get("data").toString println( s""" |database:${database} , |table:${table} , |data:${data} |""".stripMargin) /** * 判断表库进行操作,此处只针对单表,如果后面需要配置化获取配置多表判断即可 */ if (database == "rms") { if (table == "risk_task") { /** * 择取需要的数据进行数据存储 */ val dataJson = JSON.parseObject(data) val id = dataJson.get("id") val order_no = dataJson.get("order_no") val apply_time = dataJson.get("apply_time") val customer_name = dataJson.get("customer_name") /** * 建立table连接进行数据操作 */ val upsert = kuduTable.newUpsert() val row = upsert.getRow row.addInt("id", id.toString.toInt) row.addString("order_no", order_no.toString) row.addString("apply_time", apply_time.toString) row.addString("customer_name", customer_name.toString) upsert.setRow(row) kuduSession.apply(upsert) } } kuduSession.flush() println("kuduTableLimit:", kuduClientBuilder.newScannerBuilder(kuduTable) .build() .iterator().toList.count(data => true)) kuduSession.close() kuduClientBuilder.close() }).reduce(_ + _) kuduClientBuild .close() } }

部署

#!/bin/bash
flink run -m yarn-cluster \
-c com.xxxx.flink.FlinkRealTimeMain \
-p 8 \
/home/cdh/xxxx/2020/11/FlinkToKuduDemo/realtime_source_dw.jar

优化

低效点

  • 操作客户端、kudu-session、table-client在流中建立,很低效,目前还没有找到类似于Spark中Partition中的操作这种概念,也就是Flink的Task Manager中,避免task不能序列化,只能这么低效;
  • 除了广播DataStream之外也没有找到类似于广播配置源的操作;类似于任何一个对象的广播或者连接的广播;
  • 由于无法进行Partition的操作,所以就无法进行很多的KUDU客户端使用,包括KuduTable的创建还有刷新KUDU表的此类操作,都没有做到最优;
  • 具体建立Flink客户端时更多参数的优化;
  • 启动参数的优化;

优化过程

  • 后续针对这些点进行优化更新;

纠正

  • 昨天有些着急,很多点还是从微批的角度来理解了,所以其实上面低效点中关于Partition的描述并不准确,具体的处理是按照数据流来的,所以直接操作的就是每个Task Manager细化到的Task,具体后续从源码的角度来描述;

你可能感兴趣的:(Flink,大数据,实时数仓,flink,大数据,spark)