4.流式计算
来源:互联网 发布:鹅绒被和蚕丝被 知乎 编辑:程序博客网 时间:2024/05/17 21:55
流式计算,spark streaming 之前有spark core开发的积累,直接使用spark streaming来进行流式计算开发是比较节省开发成本的。
业界同样还有优秀的流式计算框架,简单介绍一下
1、Storm
响应快,纯流式,底层全是无锁编程,想做汇聚,想搞个中间状态,需要借助外部存储。
2、Samza
kafka上接了MR,使用yarn来管理集群,Topic取下来,samza处理(MR),输出放入topic
3、Flink
为了抗衡spark streaming而诞生的。之后就是双方的相互借鉴,相互学习,相互进步了。
flink与spark的主要区别在与:
flink可以按条数来进行流式处理,spark暂时还是只能按照时间来进行流式处理;
flink是纯流式,spark straming是微批处理。
简单介绍下spark streaming
说了这么多,我们来介绍介绍我们今天的主角。这里不说谁好谁差,最终是要看业务场景和具体需求,和开发成本的。
通俗点说吧,spark streaming 就是给spark core加了个时间机制,将计算间隔缩短到了秒级别的微批处理。
光说也说体现不出什么,需要对比才能知道最终的需求。
我们与storm对比一下吧
1,处理速度方面,因为strom的底层是无锁编程,spark的底层是批处理。
2,Spark Streaming是Spark的核心子框架之一。
说到Spark核心,那就不得不说RDD了。
Spark Streaming作为核心的子框架,对RDD的操作支持肯定是杠杠的,这又说明了什么?
Spark Streaming可以通过RDD和Spark上的任何框架进行数据共享和交流,这就是Spark的野心,一个堆栈搞定所有场景
3,基本会了spark core 短暂学习就可以上手spark streaming,可以结合之前spark core的开发,保证有两套计算,在线计算,离线计算,节省开发成本。
4,Spark Streaming支持多语言编程,并且各个语言之间的编程模型也是类似的,strom是java主力开发,spark是scala主力开发。函数式与指令式自行百度。
5,Spark Streaming的容错机制。Spark Streaming在读取流数据进入内存的时候会保存两个副本,计算只用一个,当出现问题的时候可以快速的切换到另外一个副本;在规定的时间内进行数据的固化;由于支持RDD操作,所以RDD本身的容错处理机制也被继承(这算是spark的优势,也是劣势吧,劣势会占用太多资源)
spark streaming基本执行流程
1,以时间片为单位划分形成数据流形成RDD(DStream),这一点要细说一下。
在spark core中,常用sc.textFile去读取数据,在spark streaming中是将读取的数据创建Dstream数据流
例如代码spark是 val RDD = sc.textFile***
spark streaming见下图,例如创建一个socket的数据流
2,对每个划分数来的RDD以批处理的方式处理数据
3,每个划分出来的RDD地处理都会提交成Job
4,后面的流程基本与spark core的流程一样了。
第一步 思路解析
1.1简单实现
首先我们要实现,spark能从kafka中消费数据。因为我们这套流程使用的spark2.1.0(scala2.11.8)+ kafka0.10.2.0。
国内没有资源讲解spark2.1 + kafka010,先啃官方文档
http://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html
创建一个简单的流去消费kafka数据
几个注意的地方
引的kafka的包是kafka010,以前的版本都是直接kafka没有后面的数字
需要配置个kafkaParams参数
使用KafkaUtils.createDirectStream方式来创建数据流
1.2保证消费高可用
网上能查到的资料,基本是两极分化
第一种是开启spark的WAL机制,这种就是将数据读两份,一份计算,另一份进行容错重算使用,这样太耗资源了,暂不考虑
第二种是使用kafka的低级api,手动维护spark消费kafka数据的偏移度,来保证消费的高可用,但是网上能查到的版本全是kafka0.8 kafka0.9的。代码完全不能拿来用。
继续啃文档
文中有说到,可以根据偏移度,创建kafka的数据流。但是我怎么获得偏移度呢?
继续看
找到个,可以获得偏移度的方法
文档中差不多就这2个了。根据偏移度创建流,再获得偏移度。
我们来撸一撸思路。
思路1:
根据最后计算偏移度读取kafka数据
||
创建流
||
业务代码计
||
计算成功,保存偏移度
||
计算结果,存至外部
思路暂时这样没错,但是我怎么去实现,根据最后偏移度读取数据呢?我怎么知道是最后的偏移度?
保存偏移度,保存到哪呢?
我们先试试输出偏移度
这里使用的println,会在spark的标准输出stdout中出现。
我们去看看
偏移度打出来。
那我们可以把那个结束偏移度保存起来,每次让DStream从这个偏移度去读新的数据,进行计算,计算完之后再获取一次偏移度,将最新的偏移度保存起来。
突然想起个问题,如果是一个新的kafka topic,我都还没有计算过呀,怎么得到最后的偏移量呢?
那我们是不是可以,在创建数据流的之前做个判断,如果是个新的topic的时候,我们以默认为0的偏移度创建是否可行呢?
最后,我们这个外部存储用什么呢?这里我们用zk,看能不能实现,但是印象中我记得zk是不能很好的支持高并发的读写,如果流式处理数据量上来,秒级别的对zk的读写,肯定会出现问题。
不过我们可以先以zk来做测试,如果zk都可以读写,其他的外部存储肯定没问题。
修改后的思路2:
判断zk中是否有保存过该计算的偏移量
如果有就接着计算,没有的话创建个新的,从0开始算
||
创建流
||
业务代码计算
||
计算成功,保存偏移度
||
计算结果,存至外部
第二步 代码讲解
2.1 maven引包
<properties> <scala.version>2.11.8</scala.version> <spark.version>2.1.0</spark.version> <hadoop.version>2.6.0</hadoop.version></properties><dependencies> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>${spark.version}</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-10_2.11</artifactId> <version>${spark.version}</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>0.10.2.0</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.10.2.0</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency>
2.2 scala代码
import org.apache.kafka.common.TopicPartitionimport org.apache.kafka.common.serialization.StringDeserializerimport org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistentimport org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribeimport org.apache.spark.streaming.kafka010.{HasOffsetRanges, KafkaUtils, OffsetRange}import org.apache.spark.{SparkConf, SparkContext, TaskContext}import org.apache.spark.streaming.{Seconds, StreamingContext}object DealFlowBills2 { /** *************************************************************************************************************** * todo zookeeper 实例化,方便后面对zk的操作,zk的代码很简单,这里就不贴了,基本照抄官方api */ val zk = ZkWork def main(args: Array[String]): Unit = { /** *************************************************************************************************************** * todo 输入参数 */ val Array(output, topic, broker, group, sec) = args /** *************************************************************************************************************** * todo spark套路 */ val conf = new SparkConf().setAppName("DealFlowBills2") val sc = new SparkContext(conf) val ssc = new StreamingContext(sc, Seconds(sec.toInt)) /** *************************************************************************************************************** * todo 1 - 准备kafka参数 */ val topics = Array(topic) val kafkaParams = Map[String, Object]( "bootstrap.servers" -> broker, "key.deserializer" -> classOf[StringDeserializer], "value.deserializer" -> classOf[StringDeserializer], "group.id" -> group, "auto.offset.reset" -> "latest", "enable.auto.commit" -> (false: java.lang.Boolean) ) /** *************************************************************************************************************** * todo 2 - 判断zk中是否有保存过该计算的偏移量 * 如果没有保存过,使用不带偏移量的计算,在计算完后保存 * 精髓就在于KafkaUtils.createDirectStream这个地方 * 默认是KafkaUtils.createDirectStream[String, String](ssc, PreferConsistent, Subscribe[String, String](topics, kafkaParams)),不加偏移度参数 * 实在找不到办法,最后啃了下源码。有个consumerStrategy消费者策略,看看里面是个什么套路; * * 原来可以执行topic,和offsets消费偏移度,这下派上用场了 * * * */ val stream = if (zk.znodeIsExists(s"${topic}offset")) { val nor = zk.znodeDataGet(s"${topic}offset") val newOffset = Map(new TopicPartition(nor(0).toString, nor(1).toInt) -> nor(2).toLong)//创建以topic,分区为k 偏移度为v的map println(s"[ DealFlowBills2 ] --------------------------------------------------------------------") println(s"[ DealFlowBills2 ] topic ${nor(0).toString}") println(s"[ DealFlowBills2 ] Partition ${nor(1).toInt}") println(s"[ DealFlowBills2 ] offset ${nor(2).toLong}") println(s"[ DealFlowBills2 ] zk中取出来的kafka偏移量★★★ $newOffset") println(s"[ DealFlowBills2 ] --------------------------------------------------------------------") KafkaUtils.createDirectStream[String, String](ssc, PreferConsistent, Subscribe[String, String](topics, kafkaParams, newOffset)) } else { println(s"[ DealFlowBills2 ] --------------------------------------------------------------------") println(s"[ DealFlowBills2 ] 第一次计算,没有zk偏移量文件") println(s"[ DealFlowBills2 ] 手动创建一个偏移量文件 ${topic}offset 默认从0号分区 0偏移度开始计算") println(s"[ DealFlowBills2 ] --------------------------------------------------------------------") zk.znodeCreate(s"${topic}offset", s"$topic,0,0") val nor = zk.znodeDataGet(s"${topic}offset") val newOffset = Map(new TopicPartition(nor(0).toString, nor(1).toInt) -> nor(2).toLong) KafkaUtils.createDirectStream[String, String](ssc, PreferConsistent, Subscribe[String, String](topics, kafkaParams, newOffset)) } /** *************************************************************************************************************** * todo 3 - 业务代码部分 * 将流中的值取出来,用于计算 */ val lines = stream.map(_.value()) lines.count().print() val result = lines .filter(_.split(",").length == 21) .map { mlines => val line = mlines.split(",") (line(3), s"${line(4)},${line(2)}") } .groupByKey() .map { case (k, v) => val result = v .flatMap { fmlines => fmlines.split(",").toList.zipWithIndex } .groupBy(_._2) .map { case (v1, v2) => v2.map(_._1) } (k, result) } /** *************************************************************************************************************** * todo 4 - 保存偏移度部分 * 计算成功后保存偏移度 */ stream.foreachRDD { rdd => val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges rdd.foreachPartition { iter => val o: OffsetRange = offsetRanges(TaskContext.get.partitionId) println(s"[ DealFlowBills2 ] --------------------------------------------------------------------") println(s"[ DealFlowBills2 ] topic: ${o.topic}") println(s"[ DealFlowBills2 ] partition: ${o.partition} ") println(s"[ DealFlowBills2 ] fromOffset 开始偏移量: ${o.fromOffset} ") println(s"[ DealFlowBills2 ] untilOffset 结束偏移量: ${o.untilOffset} 需要保存的偏移量,供下次读取使用★★★") println(s"[ DealFlowBills2 ] --------------------------------------------------------------------") // 写zookeeper zk.offsetWork(s"${o.topic}offset", s"${o.topic},${o.partition},${o.untilOffset}") // 写本地文件系统 // val fw = new FileWriter(new File("/home/hadoop1/testjar/test.log"), true) // fw.write(offsetsRangerStr) // fw.close() } } /** *************************************************************************************************************** * todo 5 - 最后结果操作部分 */ result.saveAsTextFiles(output + s"/output/" + "010") /** *************************************************************************************************************** * todo spark streaming 开始工作 */ ssc.start() ssc.awaitTermination() }}
第三步 调试实现
3.1 场景设想
回忆一下,我们之前需要实现的两个主要功能
1.flume文件采集数据不丢失,断点续采,根据崩溃时的索引,重启程序后能继续采集
2.spark streaming 消费kafka 实现数据零丢失,避免kafka重复消费,spark streaming 手动控制 kafka消费偏移量,将消费偏移量存到zookeeper,来保证消费高可用
现在文件采集的高可用是实现了,只要数据生成了。flume和kafka的启动谁先,谁后,都能保证数据不丢。
但是spark是在数据生成之前启,还是数据生成之后启,这就有点区别了。我们看看下面的两种场景。
暂时想到有2种场景
场景一
1,kafka创建topic
2,开启flume采集
3,开启spark streaming 计算
4,模拟数据源生成数据
5,开始计算
场景二
1,kafka创建topic
2,开启flume采集
3,模拟数据源生成数据
4,开启spark streamjing计算
5,开始计算
场景一是肯定没问题了,spark在数据生成之后,是肯定可以接收到数据开始计算的。
我们来模拟调试一下场景二
3.2 场景模拟
1.创建topic
2.开启flume采集
3.模拟生成数据
4.开启spark streaming 计算
5.开始计算,因为我们刚才只生成了1w,所以这里只有1w数据,但是为什么这里会多出5条,暂时没解决,环境大家批评斧正。
3.3 模拟spark宕机
造1000W数据
模式宕机
等个若干秒,再启动程序
停止生成数据,这里是生成了2416634条,我们看看spark那边有多少条
检查spark收到的条数
一共是
293434+13900+93200+101000+1217700+100100+131300+131600+129100+139000+66300 = 2416634
正好与生成数据的数量相符
3.4 待解决问题
这是实时在线计算,但是如果我们要考虑历史原始数据保留的话,该怎么操作比较合理?
怎么实现远程文件采集呢?
源代码已上传了github
https://github.com/feloxx/SparkStreaming-DirectKafka010
- 4.流式计算
- 流式计算、实时计算和离线计算
- 流式计算框架
- 流式计算介绍
- 流式计算总结
- 流式计算系统分析
- 流式计算系统
- 流式计算-Jstorm简介
- 实时流式计算Jstorm
- 流式计算框架调研
- Storm流式计算原理
- 离线计算,实时计算和流式计算的概念区分
- 流计算
- 分布式流式计算平台-S4
- 流式计算推动实时处理商业变革
- 流式计算之Storm简介
- 【流式计算】twitter storm Rationale[1]
- 【流式计算】twitter storm Tutorial [2]
- Saving Data in iOS
- 带编号的存储过程
- 第五届省赛题 奇怪的排序
- HashMap的工作原理
- 【JavaScript】2.Http中Get与Post两种请求方式的差异
- 4.流式计算
- 将整型的ASCii码值转换为对应的ASCii码字符串
- 1、RxJava2 & Retrofit2封装实践 简介
- 基础判断欧拉通路 HDU
- 树、二叉树
- golang tar gzip压缩,解压(含目录文件)
- unrecognized selector sent to instance出现的原因和解决方案
- redis
- Vert.x日志配置