Spark Streaming exactly once原理及编程示例
来源:互联网 发布:最佳睡眠时间知乎 编辑:程序博客网 时间:2024/05/17 06:56
上一节内容介绍了spark介绍了at least once以及at most once的实现原理,这里再重复一次,毕竟这些概念非常重要。 任何涉及到消息队列的服务,都会出现3个层面的问题,一个是获取数据,一个是处理数据,一个是存储数据 . 因此在谈论at least once/exactly once也要分3个阶段(这一点storm和spark是不同的,因为storm是等待处理完数据发送ACK的方式,而spark分3阶段保证)。
对于处理数据层,spark RDD天生就能保证exactly once,所以不做讨论。
通过预先写入日志WAL,让spark保存了足够的信息来恢复,不管是worker 出现问题,还是driver出现问题,都能够通过checkpoint的wal来恢复,也因此保证了at least once, 大家首先要看清楚,这里的at least once是指receiver层面。
存储层,实际就是写入数据的时候,如果此时worker或者driver 失败了,怎没保证exactly once . spark并不提供任何保护措施,需要用户自己实现。
说道这里我要说一下目前开源软件的文档问题,storm, spark这些文章极其粗糙,和原来的文档相差太多,很多东西随便略过,让用户自己去想,好歹你给个例子。
接下来继续 spark实现exactly once的问题,之前采用的是createstream,因为有2个问题,一个是wal会影响性能,另外就是并发问题,总之就是性能不行,我也不知道为什么。所以在1.3 之后的版本出现了createdirectstream.
createDirectStream默认会根据你的kafka partition个数来分配task,一个partition一个task,效率会高很多,另外spark自己会处理offset,而不是保存在zk(实际上我没觉得保存在 zk有啥问题)。那么不保存在ZK,那保存在哪里? 内存里。 那如果 spark任务出现问题怎么恢复到之前的offset? 答案是没有办法, 需要你自己处理。
因为新版api的问题,我们有几个东西需要自己看考虑,自己来做了,spark不会为你做了。
1) 由于默认不保存ZK,那么我们自己需要考虑保存在哪里? 下一次从保存的offset处理数据。假设我们仍然手动保存在ZK。
2) JOB启动的时候先判断ZK是否有保存的offsets,如果有,调用
KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, (String, String)](ssc, kafkaParams, fromOffsets, messageHandler)如果没有保存,有可能是第一次处理,调用
KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topicsSet)
因此我们的整个程序和之前createstream会有比较多的区别,整个程序如下:
package com.isesol.sparkimport org.I0Itec.zkclient.ZkClientimport org.I0Itec.zkclient.ZkConnectionimport kafka.message.MessageAndMetadataimport kafka.common.TopicAndPartitionimport kafka.utils.{ ZKGroupTopicDirs, ZkUtils }import org.apache.spark.streaming.kafka.OffsetRangeimport org.apache.spark.streaming.kafka.KafkaClusterimport org.apache.log4j._import kafka.serializer.Decoderimport kafka.serializer.StringDecoderimport kafka.message._import org.apache.spark._import org.apache.spark.streaming._import org.apache.spark.streaming.StreamingContext._import java.util.HashMapimport org.apache.kafka.clients.producer.{ KafkaProducer, ProducerConfig, ProducerRecord }import org.apache.spark.streaming.kafka._import org.apache.spark.streaming.dstream.InputDStreamobject low_streaming { def main(args: Array[String]) { Logger.getLogger("org").setLevel(Level.WARN) val conf = new SparkConf().setMaster("yarn-cluster").setAppName("this is the first spark streaming program!") val ssc = new StreamingContext(conf, Seconds(5)) val zk = "datanode01.isesol.com:2181,datanode02.isesol.com:2181,datanode03.isesol.com:2181,datanode04.isesol.com:2181,cmserver.isesol.com:2181" val brokers = "namenode02.isesol.com:9092,namenode01.isesol.com:9092,datanode04.isesol.com:9092,datanode03.isesol.com:9092" val group = "low_api1" val topics = "2001" //val topicsSet = topics.split(",").toSet val topicsSet = Set(topics) val numThreads = 2 var offsetRanges = Array[OffsetRange]() val kafkaParams = Map[String, String]("metadata.broker.list" -> brokers, "zookeeper.connect" -> "datanode01.isesol.com:2181,datanode02.isesol.com:2181,datanode03.isesol.com:2181,datanode04.isesol.com:2181,cmserver.isesol.com:2181", "group.id" -> group, "auto.offset.reset" -> "largest") var kafkaStream: InputDStream[(String, String)] = null var fromOffsets: Map[TopicAndPartition, Long] = Map() val zk_topic = new ZKGroupTopicDirs("2001_manual", topics) val zkClient = new ZkClient(zk) val child = zkClient.countChildren(s"${zk_topic.consumerOffsetDir}") /* 判断ZK是否包含offset信息,如果包含,则通过fromoffsets来开始获取数据, 如果不包含则根据kafkaparams的规则获取数据,随后会跟心获取到的offset到ZK, * fromoffsets是一个map结构,因为一个kafka topic可能不止一个partition */ if (child > 0) { for (i <- 0 until child) { val partitionOffset = zkClient.readData[String](s"${zk_topic.consumerOffsetDir}/${i}") val topicandpartition = TopicAndPartition(topics, i) fromOffsets += (topicandpartition -> partitionOffset.toLong) } println("zk contains topic offsets, then goes to messagehandler part!") /*messagehandler会把kafka数据归并为(topic_name, message)的tuple方式, 因此在取数据的时候只取 _.2 */ val messageHandler = (mmd: MessageAndMetadata[String, String]) => (mmd.topic, mmd.message()) kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, (String, String)](ssc, kafkaParams, fromOffsets, messageHandler) } else { println("zk doesn't contains topic offsets, then goes to messagehandler part!") kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topicsSet) } kafkaStream.transform { rdd => offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges rdd }.foreachRDD { rdd => for (offsets <- offsetRanges) { println(s"${offsets.topic} ${offsets.partition} ${offsets.fromOffset} ${offsets.untilOffset}") /* 更新获取到的offsets,更新到ZK实际不是原子型,因为仍然不能保证exactly once,这里仅仅是一个示例,如果通过MySQL之类的事务型数据库,是能保证的 */ val zkPath = s"${zk_topic.consumerOffsetDir}/${offsets.partition}" val zkconnection = new ZkConnection(zk) val zkUtils = new ZkUtils(zkClient, zkconnection, false) zkUtils.updatePersistentPath(zkPath, offsets.fromOffset.toString) } val line = rdd.map(x => x._2) println(line.count()) } ssc.start() ssc.awaitTermination() }}
上面的代码实际还是比较容易理解的,通过if(child > 0) 来判断是否ZK包含了数据,如果不包含则根据kafkaparam的设置来获取数据,如果包含了,说明之前已经处理了数据,那么从fromoffsets开始获取数据。 另外通过messagehandler把数据归并成tuple格式(topic_name, message) , 因此在实际处理RDD的时候,我们只需要处理 _.2的 message即可。
其他好像没有太大区别,仅仅是在读取offsets时候自己需要手动处理。 网上有很多文章极其杂乱,不知道从哪里抄袭的,连抄袭都抄不好,太让人失望。
提交jar包:
spark-submit --class com.isesol.spark.low_streaming --master yarn --deploy-mode cluster --jars spark-streaming-kafka_2.10-1.6.0-cdh5.9.0.jar --driver-memory 1g --executor-memory 1G --num-executors 5 low_streaming.jar
然后观察数据输出,以及查看ZK的offsets是否再实时变化。 另外生产环境应该是会保存到HBASE或者HDFS文件,保存在HBASE大家可以根据上一篇文章的做法即可。这里也贴一下大概代码:
rdd.foreachPartition { x => val hbaseconf = HBaseConfiguration.create() hbaseconf.set("hbase.zookeeper.quorum", "datanode01.isesol.com,datanode02.isesol.com,datanode03.isesol.com,datanode04.isesol.com,cmserver.isesol.com") hbaseconf.set("hbase.zookeeper.property.clientPort", "2181") val myTable = new HTable(hbaseconf, TableName.valueOf("test")) //myTable.setAutoFlush(false) myTable.setWriteBufferSize(3 * 1024 * 1024) x.foreach { y => { println(y) val p = new Put(Bytes.toBytes(System.currentTimeMillis().toString())) p.add(Bytes.toBytes("cf"), Bytes.toBytes("message"), Bytes.toBytes(y.toString())) myTable.put(p) } } myTable.close() }
大概也就如此了。
- Spark Streaming exactly once原理及编程示例
- Spark Streaming对Exactly Once的实现原理
- Exactly-once Spark Streaming from Apache Kafka
- Exactly-once Spark Streaming from Apache Kafka
- Spark Streaming Crash 如何保证Exactly Once
- Spark Streaming中如何实现Exactly-Once
- Spark streaming的Exactly-once容错HA机制
- Spark Streaming Crash 如何保证Exactly Once Semantics
- 第4课:Spark Streaming的Exactly Once的事务处理
- Spark Streaming 中如何实现 Exactly-Once 语义
- Trident exactly once实现原理
- Spark Streaming消息的传输与保证及编程示例
- Spark定制班第4课:Spark Streaming的Exactly-Once的事务处理和不重复输出彻底掌握
- kafka exactly once 的实现原理解析
- Spark Streaming的Exactly-One的事务处理
- Spark Streaming的Exactly-One的事务处理
- spark streaming 示例
- Spark 以及 spark streaming 核心原理及实践
- hosts 下载 android
- 关于IE8以下背景颜色的半透明的兼容性问题
- Android Activity四种启动方式
- 使用redis乐观锁实现秒杀
- 非递归二叉树
- Spark Streaming exactly once原理及编程示例
- 用JQ实现的简单轮播图
- TCP协议要点记录
- 最大公约数与最小公倍数
- std::ios::sync_with_stdio(false);--------探寻C++最快的读取文件的方案
- Android出现:Your project path contains non-ASCII characters.
- 图片上传:FileReader获取,Canvas压缩图片
- lua for循环的三个条件在第一次循环前一次性求值
- Android进阶#(5/12)独特高效的数据存储——SQLite数据库_数据库框架ActiveAndroid的使用与基本原理