SparkStreaming 2.3.1 API使用介绍

一、介绍

Spark Streaming是核心SparkAPI的扩展,可实现实时数据流的可扩展,高吞吐量,容错流处理;可以从许多来源(如Kafka,Flume,Kinesis或TCP端口)中获取数据,并且可以使用以高级函数(如map,reduce,join和window)表示的复杂算法进行处理;最后,处理后的数据可以推送到文件系统,数据库和实时仪表板。。

二、数据源

sparkstreaming既支持基本输入数据源如file、socket、rdd队列,也支持高级输入数据源如kafka,kinesis和flume。

1 基本数据源

1.1 文件系统(local hdfs等)

streamingContext.fileStream[KeyClass,valueClass,InputFormatClass](dataDirectory)

// 输入源为文件的DStream

val ds = ssc.fileStream("/opt/modules/hadoop-3.0.3/logs")

ds.print()

1.2 tcp socket

// 创建一个连接到localhost:port的DStream

val lines = ssc.socketTextStream("localhost", 9999)

val words = lines.flatMap(_.split(" "))

// 每个批次word计数

val pairs = words.map(word => (word, 1))

val wordCounts = pairs.reduceByKey(_ + _)

wordCounts.print()

1.3 queue RDD

var rddQueue = new mutable.SynchronizedQueue[RDD[Int]]

val queueStream = ssc.queueStream(rddQueue)

val result = queueStream.map(x=>(x%5,1)).reduceByKey(_+_)

result.print()

ssc.start()

while(true){

rddQueue += ssc.sparkContext.makeRDD(1 to 100,2)

}

ssc.awaitTermination()

2. 高级数据源

常用sparkstreaming消费kafka方式有两种:

2.1 基于receiver方式

算子:KafkaUtils.createStream 

方法:PUSH,从topic中去推送数据,将数据推送过来 

API:调用的Kafka高级API 

效果:此种方式企业不常用,因为接收到的数据存储在Executor的内存,会出现数据漏处理或者多处理状况 

解释:这种方法使用Receiver来接收数据。Receiver是使用Kafka高级消费者API实现的。与所有的接收者一样,通过Receiver从Kafka接收的数据存储在Spark执行程序exector中,然后由Spark Streaming启动的作业处理数据。但是,在默认配置下,这种方法可能会在失败时丢失数据。为了确保零数据丢失,您必须在Spark Streaming(在Spark 1.2中引入)中额外启用写入日志,同时保存所有接收到的Kafka数据写入分布式文件系统(例如HDFS)的预先写入日志,以便所有数据都可以在失败时恢复。 

缺点: 

  • Kafka中的主题分区与Spark Streaming中生成的RDD的分区不相关。因此,增加主题特定分区KafkaUtils.createStream()的数量只会增加在单个接收器中使用哪些主题消耗的线程的数量。在处理数据时不会增加Spark的并行性 
  • 多个kafka输入到DStream会创建多个group和topic,用于使用多个接收器并行接收数据 
  • 如果已经使用HDFS等复制文件系统启用了写入日志,则接收到的数据已经在日志中复制。因此,输入流的存储级别为存储级别StorageLevel.MEMORY_AND_DISK_SER

2.2 基于direct方式

算子:KafkaUtils.createDirectStream 

方式:PULL,到topic中去拉取数据。 

API:kafka低级API 

效果:每次到Topic的每个分区依据偏移量进行获取数据,拉取数据以后进行处理,可以实现高可用 

解释:在Spark 1.3中引入了这种新的无接收器“直接”方法,以确保更强大的端到端保证。这种方法不是使用接收器来接收数据,而是定期查询Kafka在每个topic+分partition中的最新偏移量,并相应地定义要在每个批次中处理的偏移量范围。当处理数据的作业启动时,Kafka简单的客户API用于读取Kafka中定义的偏移范围(类似于从文件系统读取文件)。请注意,此功能在Spark 1.3中为Scala和Java API引入,在Spark 1.4中针对Python API引入。 

优势: 

  • 简化的并行性:不需要创建多个输入Kafka流并将其合并。与此同时directStream,Spark Streaming将创建与使用Kafka分区一样多的RDD分区,这些分区将全部从Kafka并行读取数据。所以在Kafka和RDD分区之间有一对一的映射关系,这更容易理解和调整。
  • 效率:在第一种方法中实现零数据丢失需要将数据存储在预写日志中,这会进一步复制数据。这实际上是效率低下的,因为数据被有效地复制了两次,一次是由Kafka,另一次是由预先写入日志(Write Ahead Log)复制。此方法消除了这个问题,因为没有接收器,因此不需要预先写入日志。只要你有足够的kafka保留,消息可以从kafka恢复。
  • 精确语义:第一种方法是使用Kafka的高级API在Zookeeper中存储消耗的偏移量。传统上这是从Kafka消费数据的方式。虽然这种方法(合并日志)可以确保零数据丢失,但在某些失败情况下,很小的几率两次数据都同时丢失,发生这种情况是因为Spark Streaming可靠接收到的数据与Zookeeper跟踪的偏移之间的不一致。因此,在第二种方法中,我们使用不使用Zookeeper的简单Kafka API。在其检查点内,Spark Streaming跟踪偏移量。这消除了Spark Streaming和Zookeeper / Kafka之间的不一致性,因此Spark Streaming每次记录都会在发生故障时有效地接收一次。

请注意,这种方法的一个缺点是它不会更新Zookeeper中的偏移量,因此基于Zookeeper的Kafka监控工具将不会显示进度。但是,您可以在每个批次中访问由此方法处理的偏移量,并自己更Zookeeper

//设置检查点

ssc.checkpoint("/opt/development/checkpoint")

val topic = Set("user_pro")

val kafkaParams = Map[String,String](

"metadata.broker.list" -> "lee:9092",

"key.serializer" -> "value.serializer",

"refresh.leader.backoff.ms" -> "5000",

"group.id" -> "baymax_SparkStraming"

)

val ds1 = KafkaUtils.createDirectStream[String, String,StringDecoder,StringDecoder](ssc, kafkaParams, topic).map(_._2)

三、trainsormations操作

类似RDD和DataFrame,DStream有以下转换操作:

val ds2 = ds1.map(f=>(JSON.parseObject(f)

.getInteger("star_sign").toInt,1))

val ds3 = ds1.filter(f=>JSON.parseObject(f).getInteger("age")<=20)

val ds4 = ds3.repartition(4)

val ds5 = ds3.union(ds1)

// reduce聚合,筛选batch年龄最小的

val ds6 = ds1.reduce((x,y)=>{

if(JSON.parseObject(x).getInteger("age")>JSON

.parseObject(y).getIntValue("age"))

y

else x

})

// 每隔10秒计算前15秒年龄最小的

val ds7 = ds1.reduceByWindow((x,y)=>{

if(JSON.parseObject(x).getInteger("age")>JSON

.parseObject(y).getIntValue("age"))

y

else x

},Seconds(15),Seconds(10))

// DStream[K]=>DStream[K,Long]

implicit val cmo = new Ordering[String]{

override def compare(x:String,y:String):Int =

-JSON.parseObject(x).getInteger("age").compareTo(JSON.parseObject(y).getInteger("age"))

}

val ds8 = ds1.countByValue(2)(cmo)

val ds9 = ds2.reduceByKey(_+_)

// 每隔10秒计算各星座出现次数

val ds10 = ds2.reduceByKeyAndWindow(_+_, Seconds(10))

val ds11 = ds2.join(ds10)

val ds12 = ds2.cogroup(ds10)

特别注意:

updateStateByKey和mapWithState对比:

  • online计算:batch=>window=>全局所有时间间隔内产生的数据
  • updateStateByKey:统计全局key的状态,就算没有数据输入,也会在每个批次返回key状态
  • mapWithState:统计全局key状态,但如果没有数据输入,便不会返回之前的key的状态
  • 区别:updateStateByKey数据量太大,需要checkpoint,会占用较大内存
  • 对于没有输入,不会返回那些没有变化的key数据,checkpoint
  • 不会像updateStateByKey,占用太多内存
val ds13 = ds2.updateStateByKey(

(value:Seq[Int], state:Option[Int])=>{

val values = value.sum

Some(values + state.getOrElse(0))

})

val initialRDD = ssc.sparkContext.parallelize(List[(Int,Int)]())

val mappingFunction=

(key:Int,value:Option[Int], state:State[Int]) => {

val newState = state.getOption().getOrElse(0) + value.getOrElse(0)

state.update(newState)

(key, newState)

}

val ds14 = ds2.mapWithState(StateSpec.function(mappingFunction).initialState(initialRDD))

四、窗口transformation操作

val ds15 = ds2.window(Seconds(10))

val ds16 = ds2.countByWindow(Seconds(15),Seconds(10))

val ds17 = ds2.countByValueAndWindow(Seconds(15), Seconds(10))

val ds18 = ds2.join(ds15)

五、输出操作

1.输出文件系统

saveAsTextFiles传参为prefix和suffix,路径保存在prefix, 若hdfs,则设置为hdfs://lee:8020/

ds1.saveAsTextFiles("hdfs://lee:8020/logs/user_pro_logs", "log")

ds1.saveAsObjectFiles("/opt/data/logs/user_pro_logs", "object")

2.数据库

以HBase为例,HBase常用操作见https://blog.csdn.net/baymax_007/article/details/81213762

val tableName = "user_pro"

val cf = "info"

val columns = Array("uid","ceil","qq","age",

"birthday","height")



ds1.foreachRDD(rdd=>{

rdd.foreachPartition(records=>{

HBaseUtils1x.init()

val puts = new ArrayBuffer[Put]()

records.foreach(f=>{

val uid = JSON.parseObject(f).getString("uid")

val ceil = JSON.parseObject(f).getString("ceil")

val qq = JSON.parseObject(f).getString("qq")

val age = JSON.parseObject(f).getString("age")

val birthday = JSON.parseObject(f).getString("birthday")

/**

* 服务端建表

* create 'user_pro',

* {NAME=>'info',BLOCKSIZE=>'16384',BLOCKCACHE=>'false',TTL=>86400},

* {NUMREGIONS=>10,SPLITALGO=>'HexStringSplit'}

* rowkey设计:

* 哈希散列化,取前8位+本身

*/

val rowKey = MD5Hash.getMD5AsHex(Bytes.toBytes

(uid)).substring(0,8) + "_" + uid

val cols = Array(uid,ceil,qq,age,birthday,height,is_married,duration_of_menstruation,menstrual_cycle,star_sign,weight,province,city,recipient,recip_ceil)

try{

puts.append(HBaseUtils1x.getPutAction(rowKey,

cf, columns, cols))

}catch{

case e:Throwable => println(f)

}

})

import collection.JavaConverters._

HBaseUtils1x.addDataBatchEx(tableName, puts.asJava)

HBaseUtils1x.closeConnection()

})

})

参看文献

http://spark.apache.org/docs/latest/streaming-programming-guide.html

https://blog.csdn.net/weixin_39478115/article/details/78884876

https://www.cnblogs.com/soyo/p/7678489.html

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值