实现思路
- 第一步获取StreamingContext对象,因为要使用检查点恢复数据,所以不能使用new StreamingContext的方法获取对象,要使用StreamingContext.getOrCreate建立对象
- 创建StreamingContext对象,使用了贷出模式 ——贷出函数的方式来创建
- 从Kafka的生产者端读取数据进行分析
- 读取数据的方式采用Direct方式读取数据
- 处理读取到的数据,获取需要的值
- 调用updateStateByKey实时累加统计函数,进行累加统计
- 将结果打印到控制台,并将数据存放到Redis中
具体代码实现
import com.huadian.bigdata.jedis.JedisPoolUtil
import kafka.serializer.StringDecoder
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
object G_RedisOrderTotalStreaming {
//检查点目录
val CHECK_POINT_PATH = "file:///E:\\JavaWork\\20190811\\test5"
//Redis使用Hash存放数据
val REDIS_KEY_ORDER_TOTAL_PRICE = "orders:total:price"
def main(args: Array[String]): Unit = {
/**
* 先去查看检查点,
* createFunc:第一次运行SparkStreaming应用的时候才会执行,才会创建StreamingContext
*/
val ssc: StreamingContext = StreamingContext.getOrCreate(
CHECK_POINT_PATH,
createFunc
)
//设置日志级别
ssc.sparkContext.setLogLevel("WARN")
ssc.start()
ssc.awaitTermination()
//stop(stopSparkContext: Boolean, stopGracefully: Boolean)
ssc.stop(true,true)
}
/**
* 从源端读取数据 然后进行分析
* 类似于贷出模式 用户函数
* @param ssc
* @return
*/
def processData(ssc:StreamingContext):Unit={
val kafkaParams: Map[String, String] = Map(
"metadata.broker.list"->"bigdata-hpsk01.huadian.com:9092,bigdata-hpsk01.huadian.com:9093,bigdata-hpsk01.huadian.com:9094",
"auto.offset.reset"->"largest" //读取最新数据
)
val topics: Set[String] = Set("orderTopic")
val inputDStream: DStream[String] = KafkaUtils
.createDirectStream[String, String, StringDecoder,StringDecoder](
ssc,
kafkaParams,
topics
).map(_._2) //只需要获取Topic中每条Message中Value的值
val orderDStream: DStream[(Int, Double)] = inputDStream
//.filter(line => line.trim.length >0 && line.trim.split(",").length ==3)
.transform(rdd=>{
rdd
//过滤不合法的数据
.filter(line => line.trim.length >0 && line.trim.split(",").length ==3)
//提取字段
.map(line =>{
val splits = line.split(",")
(splits(1).toInt,splits(2).toDouble)
})
})
/**实时累加统计函数
* def updateStateByKey[S: ClassTag](
* updateFunc: (Seq[V], 表示当前批次中,相同Key的所有value集合
* Option[S] 历史状态,历史没有这个可以,所有Option类型
* ) => Option[S] 这是返回值的类型,返回值是Option,比如数据过滤的话,数据没了
* ): DStream[(K, S)]
*
*updateStateByKey和reduce函数非常非常类似
*/
val pirceDstream: DStream[(Int, Double)] = orderDStream.updateStateByKey(
(values:Seq[Double],state:Option[Double])=>{
//1、获取以前状态的值
val previousState = state.getOrElse(0.0)
//2、获取当前批次中状态数据
val currentState = values.sum
//3、更新状态发挥
Some(previousState + currentState)
})
//结果输出
pirceDstream.print()
//结果存放到Redis中
pirceDstream.foreachRDD(rdd=>{
if(!rdd.isEmpty()){
//减少分区数目
rdd.coalesce(1).foreachPartition(iter=>{
//自定义Redis连接类,使用了Jedis连接了Redis数据库
val jedis = JedisPoolUtil.getJedisPoolInstance.getResource
iter.foreach{
case(provinceid,orderTotal)=>{
jedis.hset(REDIS_KEY_ORDER_TOTAL_PRICE,
provinceid.toString,
orderTotal.toString)
}
}
JedisPoolUtil.release(jedis)
})
}
})
}
/**
* 创建StreamingContext对象
* 类似于贷出模式 贷出函数
* @return
*/
def createFunc():StreamingContext={
val conf = new SparkConf()
.setMaster("local[3]") //为什么启动3个,有一个Thread运行Receiver
.setAppName("F_CheckPointOrderTotalStreaming")
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
//日志级别
ssc.sparkContext.setLogLevel("WARN")
processData(ssc)
ssc.checkpoint(CHECK_POINT_PATH)
ssc
}
}
代码优化
上面的代码中,会将所有的结果都输出
即没有输入数据时,也会输出原来的处理结果
可以对其进行优化,即输出有数据更新的结果
删除原有的累加代码块
/**
* todo:
* 下面函数,针对每一条数据进行更新状态,
* 并不是将同一批次相同的Key的value聚合在一起更新状态
* def function[KeyType, ValueType, StateType, MappedType](
* mappingFunction: (KeyType, Option[ValueType], State[StateType]) => MappedType
*
* KeyType:key的类型
* StateType:表示 状态的类型,此时来说,就是销售营业额
* ):
*/
val mappingFunc = (provinceId:Int,orderPrice:Option[Double],state:State[Double])=>{
//获取以前状态的值
val previouState = state.getOption().getOrElse(0.0)
//更新的状态
val lastestState = previouState + orderPrice.getOrElse(0.0)
state.update(lastestState)
//MappedType :最终获取的 结果的类型 ,就是DSTream状态统计输出RDD的类型
(provinceId,lastestState)
}
val pirceDstream: MapWithStateDStream[Int, Double, Double, (Int, Double)] = orderDStream.mapWithState(StateSpec.function(mappingFunc))