开发Flink实时分析系统
, 通过流的方式读取Kafka中的消息, 进而分析数据
(详细源码在最后!!)
业务
- 实时分析频道热点
- 实时分析频道PV/UV
- 实时分析频道新鲜度
- 实时分析频道地域分布
- 实时分析运营商平台
- 实时分析浏览器类型
技术
- Flink实时处理算子
- 使用
CheckPoint
和水印
解决Flink生产上遇到的问题(网络延迟、丢数据) - Flink整合Kafka
- Flink整合HBase
1. 搭建【Flink实时数据分析系统】项目环境
1.1 导入Maven项目依赖
- pom.xml文件中的依赖导入到
real-process
项目的pom.xml real-process
模块添加scala支持- main和test创建
scala
文件夹,并标记为源代码和测试代码目录
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>flink_pyg</artifactId>
<groupId>com.xu.flinkpyg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>real-process</artifactId>
<properties>
<scala.version>2.11</scala.version>
<flink.version>1.6.1</flink.version>
<hadoop.version>2.7.5</hadoop.version>
<hbase.version>2.0.0</hbase.version>
</properties>
<dependencies>
<!--kafka 客户端-->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_${scala.version}</artifactId>
<version>0.10.1.0</version>
</dependency>
<!--flink对接kafka:导入flink使用kafka的依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.10_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--批处理-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--导入scala的依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--模块二 流处理-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--数据落地flink和hbase的集成依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-hbase_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase.version}</version>
</dependency>
<!--<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.0.0</version>
</dependency>-->
<!--hbase依赖于hadoop-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
<!--xml.parser冲突 flink hdfs-->
<exclusions>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
<!--数据同步:canal 和 hadoop protobuf-->
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--对象和json 互相转换的-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>com.google.code.findbugs:jsr305</exclude>
<exclude>org.slf4j:*</exclude>
<exclude>log4j:*</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<!-- Do not copy the signatures in the META-INF folder.
Otherwise, this might cause SecurityExceptions when using the JAR. -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.itheima.realprocess.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1.2. 创建项目包结构
包名 | 说明 |
---|---|
com.xu.realprocess.util | 存放工具类 |
com.xu.realprocess.bean | 存放实体类 |
com.xu.realprocess.task | 存放具体的分析任务 每一个业务都是一个任务,对应的分析处理都写在这里 |
1.3. 导入实时系统Kafka/Hbase配置
resources
目录
application.conf
如下:
#
#kafka的配置
#
# Kafka集群地址
bootstrap.servers="node01:9092,node02:9092,node03:9092"
# ZooKeeper集群地址
zookeeper.connect="node01:2181,node02:2181,node03:2181"
# Kafka Topic名称
input.topic="pyg"
# 消费组ID
group.id="pyg"
# 自动提交拉取到消费端的消息offset到kafka
enable.auto.commit="true"
# 自动提交offset到zookeeper的时间间隔单位(毫秒)
auto.commit.interval.ms="5000"
# 每次消费最新的数据
auto.offset.reset="latest"
这里注意zookeeper的地址,我的是:
zookeeper.connect="node01:2181,node02:2181,node03:2181/kafka"
log4j.properties
如下:
log4j.rootLogger=warn,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n
1.4. 获取配置文件API介绍
ConfigFactory.load()介绍
- 使用
ConfigFactory.load()
可以自动加载配置文件中的application.conf
文件(注意:名字一定不要写错,否则无法加载
),并返回一个Config对象 - 使用Config对象可以获取到配置文件中的配置项
application.conf
文件是一个properties文件,存放key-value键值对的数据
常用API
方法名 | 说明 |
---|---|
getString("key") | 获取配置文件中指定key的值对应的字符串 |
getInt(“key”) | 获取配置文件中指定key的值对应的整型数字 |
getLong(“key”) | 同上 |
getBoolean(“key”) | 同上 |
1.5. 编写scala代码读取配置工具类
在com.xu.realprocess.util
包下创建GlobalConfigUtil
单例对象(object)
步骤
- 使用
ConfigFactory.load
获取配置对象 - 编写方法加载
application.conf
配置 - 添加一个
main
方法测试,工具类是否能够正确读取出配置项。
package com.xu.realprocess.util
import com.typesafe.config.{Config, ConfigFactory}
// 配置文件加载类
object GlobalConfigUtil {
// 通过工厂加载配置
val config:Config = ConfigFactory.load()
val bootstrapServers = config.getString("bootstrap.servers")
val zookeeperConnect = config.getString("zookeeper.connect")
val inputTopic = config.getString("input.topic")
val groupId = config.getString("group.id")
val enableAutoCommit = config.getString("enable.auto.commit")
val autoCommitIntervalMs = config.getString("auto.commit.interval.ms")
val autoOffsetReset = config.getString("auto.offset.reset")
def main(args: Array[String]): Unit = {
println(bootstrapServers)
println(zookeeperConnect)
println(inputTopic)
println(groupId)
println(enableAutoCommit)
println(autoCommitIntervalMs)
println(autoOffsetReset)
}
}
2 初始化Flink流式计算环境
步骤
- 创建
App
单例对象 - 创建main方法,获取
StreamExecutionEnvironment
运行环境 - 设置流处理的时间为
EventTime
,使用数据发生的时间来进行数据处理 - 设置Flink的并行度
- 编写测试代码,测试Flink程序是否能够正确执行
注意:
- 一定要导入
import org.apache.flink.api.scala._
隐式转换,否则Flink程序无法执行- 到导入
org.apache.flink.streaming.api
下的TimeCharacteristic
,否则没有EventTime
3. Flink添加checkpoint容错支持
Checkpoint
是Flink实现容错机制最核心的功能,它能够根据配置周期性地基于Stream中各个Operator的状态来生成Snapshot
,从而将这些状态数据定期持久化存储下来,当Flink程序一旦意外崩溃时,重新运行程序时可以有选择地从这些Snapshot进行恢复,从而修正因为故障带来的程序数据状态中断。
步骤
-
Flink envrionment中添加
checkpoint
支持 -
运行Flink程序测试checkpoint是否配置成功(检查HDFS中是否已经保存snapshot数据)
-
在Flink流式处理环境中,添加以下checkpoint的支持,确保Flink的高容错性,数据不丢失。
// // 保证程序长时间运行的安全性进行checkpoint操作 // // 5秒启动一次checkpoint env.enableCheckpointing(5000) // 设置checkpoint只checkpoint一次 env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE) // 设置两次checkpoint的最小时间间隔 env.getCheckpointConfig.setMinPauseBetweenCheckpoints(1000) // checkpoint超时的时长 env.getCheckpointConfig.setCheckpointTimeout(60000) // 允许的最大checkpoint并行度 env.getCheckpointConfig.setMaxConcurrentCheckpoints(1) // 当程序关闭的时,触发额外的checkpoint env.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION) // 设置checkpoint的地址 env.setStateBackend(new FsStateBackend("hdfs://node01:8020/flink-checkpoint/"))
-
启动
HDFS
-
启动
Flink程序
测试 -
如果测试成功,在HDFS中应该生成如下几个目录
4. Flink整合Kafka
4.1. Flink读取Kafa数据
步骤
- 配置Kafka连接属性
- 使用
FlinkKafkaConsumer010
整合Kafka - 添加一个source到当前Flink环境
- 启动
zookeeper
- 启动
kafka
- 运行Flink程序测试是否能够从Kafka中消费到数据
实现
-
配置Kafka连接属性
// // 整合Kafka // val properties = new Properties() // # Kafka集群地址 properties.setProperty("bootstrap.servers",GlobalConfigUtil.bootstrapServers) // # ZooKeeper集群地址 properties.setProperty("zookeeper.connect",GlobalConfigUtil.zookeeperConnect) // # Kafka Topic名称 properties.setProperty("input.topic",GlobalConfigUtil.inputTopic) // # 消费组ID properties.setProperty("group.id",GlobalConfigUtil.groupId) // # 自动提交拉取到消费端的消息offset到kafka properties.setProperty("enable.auto.commit",GlobalConfigUtil.enableAutoCommit) // # 自动提交offset到zookeeper的时间间隔单位(毫秒) properties.setProperty("auto.commit.interval.ms",GlobalConfigUtil.autoCommitIntervalMs) // # 每次消费最新的数据 properties.setProperty("auto.offset.reset",GlobalConfigUtil.autoOffsetReset) val consumer = new FlinkKafkaConsumer010[String]( GlobalConfigUtil.inputTopic, new SimpleStringSchema(), properties )
-
添加一个source到当前Flink环境
val kafkaDataStream: DataStream[String] = env.addSource(consumer)
-
打印DataStream中的数据
kafkaDataStream.print()
-
启动
zookeeper
-
启动
kafka
-
运行Flink程序
-
运行上报服务系统
-
启动消息生成器, 测试是否能够从Kafka中消费到数据
如果Flink从Kafka消费成功会打印以下数据,就证明我们的代码是正确的。
{
"count": 1,
"message": "{\"browserType\":\"谷歌浏览器\",\"categoryID\":6,\"channelID\":4,\"city\":\"America\",\"country\":\"china\",\"entryTime\":1544601660000,\"leaveTime\":1544634060000,\"network\":\"联通\",\"produceID\":4,\"province\":\"china\",\"source\":\"百度跳转\",\"userID\":13}",
"timeStamp": 1553188417573
}
4.2. Kafka消息解析为元组
步骤
- 使用map算子,遍历kafka中消费到的数据
- 使用FastJSON转换为JSON对象
- 将JSON的数据解析成一个元组
- 打印map映射后的元组数据
- 测试是否能够正确解析
代码
-
使用map算子,将kafka中消费到的数据,使用FastJSON准换为JSON对象
-
将JSON的数据解析成一个元组
object App { def main(args: Array[String]): Unit = { ... // 使用map算子,将kafka中消费到的数据 val tupleDataStream = kafkaDataStream.map { msgJson => // 使用FastJSON转换为JSON对象 val jsonObject = JSON.parseObject(msgJson) val count = jsonObject.getLong("count") val message = jsonObject.getString("message") val timestamp = jsonObject.getLong("timeStamp") // 将JSON的数据解析封装到元组中 (count, message,timestamp) } tupleDataStream.print() ... } }
-
打印经过map映射后的元组数据,测试是否能够正确解析
(1,{"browserType":"360浏览器","categoryID":6,"channelID":15,"city":"ShiJiaZhuang","country":"china","entryTime":1544619660000,"leaveTime":1544634060000,"network":"移动","produceID":2,"province":"Beijing","source":"直接输入","userID":8},1557474908321)
4.3. Flink封装点击流消息为样例类
步骤
- 创建一个
ClickLog
样例类中来封装消息 - 使用map算子将数据封装到
ClickLog
样例类
代码
- 在bean包中,创建
ClickLog
样例类,添加以下字段
- 频道ID(channelID)
- 产品类别ID(categoryID)
- 产品ID(produceID)
- 国家(country)
- 省份(province)
- 城市(city)
- 网络方式(network)
- 来源方式(source)
- 浏览器类型(browserType)
- 进入网站时间(entryTime)
- 离开网站时间(leaveTime)
- 用户的ID(userID)
-
在
ClickLog
伴生对象中实现apply
方法 -
使用FastJSON的
JSON.parseObject
方法将JSON字符串构建一个ClickLog
实例对象 -
使用map算子将数据封装到
ClickLog
样例类 -
在样例类中编写一个main方法,传入一些JSON字符串测试是否能够正确解析
-
重新运行Flink程序,测试数据是否能够完成封装
使用JSONObject.get(“key”)获取到的数据是一个Any类型,要调用toString方法转换为String类型
4.4. 封装Kafka消息为Message样例类
步骤
- 创建一个
Message
样例类,将ClickLog、时间戳、数量封装 - 将Kafka中的数据整个封装到Message类中
- 运行Flink测试
测试
依次运行:
执行 ReportApplication 启动SpringBoot;
执行App中的main方法
执行消息生成main方法:ClickLogGenerator
可能出现的问题:
启动app出现:
WARN - Bootstrap broker node01:9092 disconnected
WARN - Bootstrap broker node03:9092 disconnected
WARN - Bootstrap broker node02:9092 disconnected
这是kafka版本不一致导致的,经过调查发现,LInux服务上安装的版本为:kafka_2.11-0.10.0.0 , 而程序中pom.xml中,配置的为10.1.0版,修改为和服务器版本一致即可!!
另外,关于配置文件中 zookeeper集群的地址的配置,一般默认是没有后缀的,但是也有一些人习惯在kafka中增加配合,这个配置路径,在kafka的conf中的 server.properties
,比如我配置的内容就为:
zookeeper.connect="node01:2181,node02:2181,node03:2181/kafka"
注意即可!!!
运行结果如下:
5. Flink添加水印支持
水印(watermark)就是一个时间戳
,Flink可以给数据流添加水印,可以理解为:Flink收到一条消息后,额外给这个消息添加了一个时间字段,这就是添加水印
。
- 水印并不会影响原有Eventtime
- 一般会设置水印时间,比Eventtime小几秒钟
- 当数据流添加水印后,会按照水印时间来触发窗口计算
- 当接收到的
水印时间 >= 窗口的endTime
,则触发计算
观察下图:
60号
消息的EventTime为10:10:00
, 正好符合10:00:00到10:10:00这个时间窗口的endtime。正常情况, 该消息应该被这个窗口计算的。但是当发生网络延迟的时候,该消息可能会晚到几秒钟,那么当它到达flink时,该窗口已经运算完毕。为了解决该问题,我们为该消息设置watermark时间
为10:09:57
,当它到达flink时,会把该watermark时间设置为窗口的当前时间,由于小于endtime,所以该消息到达后并不会立即计算。直到一个携带watermark时间大于或者等于endtime的时候,窗口计算才会被触发。这样就可以有效的解决由于网络延迟造成的数据计算不精确的情况。
-
在
App.scala
中添加水印支持tupleDataStream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[Message]{ // 当前时间戳 var currentTimestamp = 0l //延迟时间 var maxDelayTime = 2000l // 获取水印时间 override def getCurrentWatermark: Watermark = { new Watermark(currentTimestamp - maxDelayTime) } // 获取EventTime override def extractTimestamp(element: Message, previousElementTimestamp: Long): Long = { // 比较当前消息的时间戳和上一个消息的时间戳,取最大值 currentTimestamp = Math.max(element.timeStamp,previousElementTimestamp) currentTimestamp } })
-
启动执行测试,观察输出
Message(1,1557475915909,ClickLog(火狐,9,14,ShiJiaZhuang,china,1544612460000,1544634060000,移动,2,HeNan,必应跳转,11))
源码:
package com.xu.realprocess.bean
import com.alibaba.fastjson.JSON
object ClickLog {
def apply(json:String):ClickLog={
// 先把json转换为JSONObject
val jsonObject = JSON.parseObject(json)
// 提取jsonObject中的各个属性,赋值给样例类
val channelID = jsonObject.getString("channelID")
val categoryID = jsonObject.getString("categoryID")
val produceID = jsonObject.getString("produceID")
val country = jsonObject.getString("country")
val province = jsonObject.getString("province")
val city = jsonObject.getString("city")
val network = jsonObject.getString("network")
val source = jsonObject.getString("source")
val browserType = jsonObject.getString("browserType")
val entryTime = jsonObject.getString("entryTime")
val leaveTime = jsonObject.getString("leaveTime")
val userID = jsonObject.getString("userID")
ClickLog(
channelID,
categoryID,
produceID,
country,
province,
city,
network,
source,
browserType,
entryTime,
leaveTime,
userID
)
}
}
//频道ID(channelID)
//产品类别ID(categoryID)
//产品ID(produceID)
//国家(country)
//省份(province)
//城市(city)
//网络方式(network)
//来源方式(source)
//浏览器类型(browserType)
//进入网站时间(entryTime)
//离开网站时间(leaveTime)
//用户的ID(userID)
case class ClickLog (
var channelID:String,
var categoryID:String,
var produceID:String,
var country:String,
var province:String,
var city:String,
var network:String,
var source:String,
var browserType:String,
var entryTime:String,
var leaveTime:String,
var userID:String
)
package com.xu.realprocess.bean
case class Message (
var clickLog:ClickLog,
var count:Long,
var timeStamp:Long
)
package com.xu.realprocess.util
import com.typesafe.config.{Config, ConfigFactory}
// 配置文件加载类
object GlobalConfigUtil {
// 通过工厂加载配置
val config:Config = ConfigFactory.load()
val bootstrapServers = config.getString("bootstrap.servers")
val zookeeperConnect = config.getString("zookeeper.connect")
val inputTopic = config.getString("input.topic")
val groupId = config.getString("group.id")
val enableAutoCommit = config.getString("enable.auto.commit")
val autoCommitIntervalMs = config.getString("auto.commit.interval.ms")
val autoOffsetReset = config.getString("auto.offset.reset")
def main(args: Array[String]): Unit = {
println(bootstrapServers)
println(zookeeperConnect)
println(inputTopic)
println(groupId)
println(enableAutoCommit)
println(autoCommitIntervalMs)
println(autoOffsetReset)
}
}
package com.xu.realprocess
import java.util.Properties
import com.alibaba.fastjson.JSON
import com.xu.realprocess.bean.{ClickLog, Message}
import com.xu.realprocess.util.GlobalConfigUtil
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.streaming.api.{CheckpointingMode, TimeCharacteristic}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.runtime.state.filesystem.FsStateBackend
import org.apache.flink.streaming.api.environment.CheckpointConfig
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer010
object App {
def main(args: Array[String]): Unit = {
// ---------------初始化FLink的流式环境--------------
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
// 设置处理的时间为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 设置并行度
env.setParallelism(1)
// 本地测试 加载本地集合 成为一个DataStream 打印输出
/*val localDataStream: DataStream[String] = env.fromCollection(
List("hadoop", "hive", "hbase", "flink")
)
localDataStream.print()*/
// 添加Checkpoint的支持
// 5s钟启动一次checkpoint
env.enableCheckpointing(5000)
// 设置checkpoint只checkpoint一次
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
// 设置两次checkpoint的最小时间间隔
env.getCheckpointConfig.setMinPauseBetweenCheckpoints(1000)
// 设置checkpoint的超时时长
env.getCheckpointConfig.setCheckpointTimeout(60000)
// 最大并行度
env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
// 当程序关闭时,触发额外的checkpoint
env.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
// 设置checkpoint的地址
env.setStateBackend(new FsStateBackend("hdfs://node01:8020/flink-checkpoint/"))
// -------------整合Kafka----------
val properties = new Properties()
// # Kafka集群地址
properties.setProperty("bootstrap.servers", GlobalConfigUtil.bootstrapServers)
// # ZooKeeper集群地址
properties.setProperty("zookeeper.connect", GlobalConfigUtil.zookeeperConnect)
// # Kafka Topic名称
properties.setProperty("input.topic", GlobalConfigUtil.inputTopic)
// # 消费组ID
properties.setProperty("group.id", GlobalConfigUtil.groupId)
// # 自动提交拉取到消费端的消息offset到kafka
properties.setProperty("enable.auto.commit", GlobalConfigUtil.enableAutoCommit)
// # 自动提交offset到zookeeper的时间间隔单位(毫秒)
properties.setProperty("auto.commit.interval.ms", GlobalConfigUtil.autoCommitIntervalMs)
// # 每次消费最新的数据
properties.setProperty("auto.offset.reset", GlobalConfigUtil.autoOffsetReset)
// 话题 反序列化器 属性集合
val consumer = new FlinkKafkaConsumer010[String](GlobalConfigUtil.inputTopic, new SimpleStringSchema(), properties)
val kafkaDatastream: DataStream[String] = env.addSource(consumer)
// kafkaDatastream.print()
// JSON -> 元组
val tupleDataStream = kafkaDatastream.map {
msgJson =>
val jsonObject = JSON.parseObject(msgJson)
val message = jsonObject.getString("message")
val count = jsonObject.getLong("count")
val timeStamp = jsonObject.getLong("timeStamp")
// (message,count,timeStamp)
// (ClickLog(message),count,timeStamp)
Message(ClickLog(message), count, timeStamp)
}
println("开始打印:")
tupleDataStream.print()
println("打印结束!!")
// -----------添加水印支持--------------------
var watermarkDataStrem = tupleDataStream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[Message] {
var currentTimeStamp = 0l
// 延迟时间
var maxDelayTime = 2000l
// 获取当前时间戳
override def getCurrentWatermark: Watermark = {
new Watermark(currentTimeStamp - maxDelayTime)
}
// 获取事件时间
override def extractTimestamp(element: Message, previousElementTimestamp: Long): Long = {
currentTimeStamp = Math.max(element.timeStamp, previousElementTimestamp)
currentTimeStamp
}
})
// 执行任务
env.execute("real-process")
}
}