背景
- 接入Kafka实时数据经过数据处理写入HBase,后续会应用于类似变量系统以及实时日志中,对于变量系统这类中间需要做实时缓存宽表可能使用HBase连接极其频繁,所以是使用客户端还是Sink的方式就看实际情况而定,具体数据处理后的落库Sink还是比较方便的;
摘要
关键字
- Flink,Sink,HBase,数据处理,数据流转
设计
- 使用的是Max Well数据源,将业务数据接入Kafka,Flink-Source接入Kafka,中间经过数据流转将数据存储到HBase作实时表;
实现
- 说明
此处的处理没有写成项目中使用的比较复杂的可配置化的形式,所以在invoke中做了过滤,也就是只针对单表的操作; - 依赖
<scala.main.version>2.11</scala.main.version>
<flink.version>1.9.1</flink.version>
<hbase.version>2.1.0</hbase.version>
<!--flink-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_${scala.main.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_${scala.main.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--flink kafka-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_${scala.main.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--flink table & sql-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-scala-bridge_${scala.main.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner_${scala.main.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--导入flink连接redis的文件-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-redis_${scala.main.version}</artifactId>
<version>${flink.redis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-elasticsearch6_${scala.main.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--rocksdb 与flink 进行整合依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb_2.11</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep-scala_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase.version}</version>
<!-- <scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.2.1</version>
<!-- <scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>${hbase.version}</version>
<!-- <scope>provided</scope>-->
</dependency>
- main
object MyHbaseSinkTest {
def main(args: Array[String]): Unit = {
//环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
/**
* 获取基础参数
*/
val bootstrapserversnew = Contant.BOOTSTRAP_SERVERS_NEW
import org.apache.flink.api.scala._
/**
* 定义kafka-source得到DataStream
*/
val topics = "vs_merging_middle_topic"
//将kafka中数据反序列化,
val valueDeserializer: DeserializationSchema[String] = new SimpleStringSchema()
val properties = new Properties()
properties.put("bootstrap.servers", bootstrapserversnew)
properties.put("group.id", "flink_hbase_sink_consumer2")
properties.put("auto.offset.reset", Contant.AUTO_OFFSET_RESET_VALUE)
println(Contant.BOOTSTRAP_SERVERS_NEW)
val kafkaSinkDStream = env.addSource(new FlinkKafkaConsumer[String](topics, valueDeserializer, properties))
kafkaSinkDStream.flatMap(data => {
val jsonData = JSON.parseObject(data)
val table = jsonData.getString("table")
val rowkey = jsonData.getJSONObject("data").get("id").toString
val jsonResult = jsonData.getJSONObject("data")
import scala.collection.JavaConversions._
jsonResult.keySet().map(key => {
HbaseValueBean("variable_system:" + table, rowkey, "info", key, jsonResult.get(key).toString)
})
})
.addSink(MyHbaseSinkSingle)
env.execute(this.getClass.getSimpleName)
}
}
- bean
case class HbaseValueBean(
tableName : String ,
rowkey : String ,
family : String ,
column : String ,
value : String
)
- HBase-Sink
object MyHbaseSinkSingle extends RichSinkFunction[HbaseValueBean] {
private var putsResult: List[Put] = collection.immutable.List[Put]()
private var hbaseConn: Connection = null
private var maxSize = 100
/**
* 开始,打开连接
*
* @param parameters
*/
override def open(parameters: Configuration): Unit = {
hbaseConn = HBaseUtils.getHbaseConnNotPoolNew
maxSize = parameters.getInteger("maxSize" , 100)
// val lastInvokeTime = System.currentTimeMillis
}
/**
* 数据处理
*
* @param value
* @param context
*/
override def invoke(value: HbaseValueBean, context: SinkFunction.Context[_]): Unit = {
if (value.tableName == "variable_system:risk_task") {
import scala.collection.JavaConversions._
/**
* rowkey与put
*/
val put = new Put(value.rowkey.getBytes())
PutUtils.setDataString(put , value.family , value.column , value.value)
putsResult = putsResult :+ put
println(s"puts-size:${putsResult.size}")
/**
* 判断输出
*/
if (putsResult.size() >= maxSize) {
val table = hbaseConn.getTable(TableName.valueOf(value.tableName))
table.put(putsResult)
println("进行sink")
println(s"table:${value.tableName} , 已经达到:${maxSize} , 已存储;")
println(s"puts:${putsResult}")
/**
* 因为Java与Scala集合转换,所以这里是没有scala的清除方法的
*/
putsResult = collection.immutable.List[Put]()
table.close()
}
}
}
/**
* 满足条件输出数据并且关闭连接
*/
override def close(): Unit = {
hbaseConn.close()
}
}
- Hbase-Connect
def getHbaseConnNotPoolNew: Connection = {
var conf: Configuration = HBaseConfiguration.create
conf.set("hbase.zookeeper.quorum", "host:port")
conf.set("hbase.zookeeper.property.clientPort", "port")
conf.set("hbase.master", "16000")
conf.set("hbase.rootdir", "dir")
val conn = ConnectionFactory.createConnection(conf)
conn
}
- HBase-Utiles
object PutUtils {
def setDataString(put: Put,cf:String,col:String,data:String): Unit ={
put.addColumn(Bytes.toBytes(cf) , Bytes.toBytes(col) , Bytes.toBytes(data))
}
def setDataStringGetPut(put: Put,cf:String,col:String,data:String): Put ={
put.addColumn(Bytes.toBytes(cf) , Bytes.toBytes(col) , Bytes.toBytes(if(data!= null && data != "") data else "null"))
}
}
部署
#!/bin/bash
flink run -m yarn-cluster \
-c com.xxxx.flink.MyHbaseSinkTest \
-p 8 \
/home/cdh/xxxx/2020/11/FlinkToKuduDemo/realtime_source_dw.jar
注意事项
- 不能够以Spark Streaming的方式来理解Fink的Source和Sink,如果使用客户端是无法针对每个分区进行连接数据处理的,使用Sink可以建立全局连接进行数据存储;
- 由上面,全局连接,全局数据处理导致的问题就是不能够每个分区建立线程不安全的集合进行数据存储,必须使用线程安全的集合,也就是不可变的集合进行数据处理,那么使用了Scala之间的集合转换就要注意方法的使用,很多Java结合的方法Scala是没有的,所以一般的清空操作还是使用地址替换重新复制覆盖的方式来进行
- addSink的之前的DataStream的数据类型一定是与自定义Sink的操作类型一致的,这个刚开始如果没有注意到就很没有头绪,所以是针对最后的数据类型进行处理存储的;