背景
- 互联网金融,面对的业务方较多;风控部门的数据分析师,策略分析师,反欺诈分析师等,目前的数据量这些分析师使用Python以及MySQL是无法满足快速高效的分析的;商城、运营部门等的报表看板,定制化用户行为分析等。;目前的自主分析是使用的开源产品Superset做一部分的改造,接入Druid,ES,Impala,分析师们已经全部转到我们的平台,大部分的使用都是基于我们数仓的DWS,但是除此之外实时数据没有完全接入,这是目前的痛点,也是最需要做的;尝试使用HBase做映射使用Impala分析,但是只能按照固定的键高效查询,无法满足更加多元化的定制化分析场景;
- 之前一直使用Spark Streaming进行实时数据的流转,此次我打算用Flink来做,此篇是一个测试过程,操作也是十分粗糙,比较低效;
摘要
关键字
- Flink将Maxwell实时数据写入KUDU
- Flink第一步
设计
- 完整的设计如下
- 使用MySQL作为配置表,每日一次广播
- 因为实时接入的业务库很多,所以需要进行配置化写入,需要才做添加;
- 配置库名,表名详细参数等;
- Flink对接MaxWell将需要的数据写入KUDU;
- Impala集成KUDU于飞天(BI数据分析平台),数分、策略、算法等进行使用;
说明
- 因为是一个简单的开始Demo,刚开始使用Flink写入,所以有很多待优化的地方,也是需要跟进学习的地方;
- Demo为使用KUDU客户端,并非标准的Flink-KUDU-sink;
- Demo只是关于表的创建与写入,过程顺畅之后多表无非是根据不同的配置进行不同的处理了;
- 这篇帖子是针对与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<br/>
* Copyright (c) ,2020 , jackson <br/>
* This program is protected by copyright laws. <br/>
* 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,具体后续从源码的角度来描述;