Spark streaming+kafka实战教程

本文转载请自:http://qifuguang.me/2015/12/24/Spark-streaming-kafka实战教程/

概述

Kafka是一个分布式的发布-订阅式的消息系统,简单来说就是一个消息队列,好处是数据是持久化到磁盘的(本文重点不是介绍kafka,就不多说了)。Kafka的使用场景还是比较多的,比如用作异步系统间的缓冲队列,另外,在很多场景下,我们都会如如下的设计:

将一些数据(比如日志)写入到kafka做持久化存储,然后另一个服务消费kafka中的数据,做业务级别的分析,然后将分析结果写入HBase或者HDFS

正因为这个设计很通用,所以像Storm这样的大数据流式处理框架已经支持与kafka的无缝连接。当然,作为后起之秀,Spark同样对kafka提供了原生的支持。

本文要介绍的是Spark streaming + kafka的实战。

目的

本文要实现的是一个很简单的功能:

有日志数据源源不断地进入kafka,我们用一个spark streaming程序从kafka中消费日志数据,这些日志是一个字符串,然后将这些字符串用空格分割开,实时计算每一个单词出现的次数。

具体实现

部署zookeeper

  1. 到官方网站下载zookeeper
  2. 解压
  3. 到zookeeper的bin目录下,使用如下命令启动zookeeper:

    1
    
    ./zkServer.sh start ../conf/zoo.cfg 1>/dev/null 2>&1 &
    
  4. 使用ps命令查看zookeeper是否已经真的启动

部署kafka

  1. 到官方网站下载kafka
  2. 解压
  3. 到kafka的bin目录下使用如下命令启动kafka

    1
    
    ./kafka-server-start.sh ../config/server.properties 1>/dev/null 2>&1 &
    
  4. 使用ps命令查看kafka是否已经启动

编写spark程序

  1. 使用itellij新建maven工程
  2. pom.xml中添加spark-streaming相关的依赖,因为要和kafka相结合,所以还需要添加spark-streaming-kafka包,pom文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    
    
             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">
        4.0.0
    
        com.winwill.spark
        kafka-spark-demo
        1.0-SNAPSHOT
    
        
            
                org.apache.spark
                spark-core_2.10
                1.5.2
            
            
                org.apache.spark
                spark-streaming_2.10
                1.5.2
            
            
                org.apache.spark
                spark-streaming-kafka_2.10
                1.5.2
            
            
                org.scala-lang
                scala-library
                2.10.6
            
        
    
    
  3. 编写业务逻辑,本例中我们使用directStream,关于directStream和stream的差别下文会详细介绍。我们创建一个KafkaSparkDemoMain类,代码如下,代码中已有详细注释,就不多解释了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.winwill.spark

import kafka.serializer.StringDecoder
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.{Duration, StreamingContext}
import org.apache.spark.streaming.kafka.KafkaUtils

/**
 * @author qifuguang
 * @date   15/12/25 17:13
 */
object KafkaSparkDemoMain {
    def main(args: Array[String]) {
        val sparkConf = new SparkConf().setMaster("local[2]").setAppName("kafka-spark-demo")
        val scc = new StreamingContext(sparkConf, Duration(5000))
        scc.checkpoint(".") // 因为使用到了updateStateByKey,所以必须要设置checkpoint
        val topics = Set("kafka-spark-demo") //我们需要消费的kafka数据的topic
        val kafkaParam = Map(
                "metadata.broker.list" -> "localhost:9091" // kafka的broker list地址
            )

        val stream: InputDStream[(String, String)] = createStream(scc, kafkaParam, topics)
        stream.map(_._2)      // 取出value
            .flatMap(_.split(" ")) // 将字符串使用空格分隔
            .map(r => (r, 1))      // 每个单词映射成一个pair
            .updateStateByKey[Int](updateFunc)  // 用当前batch的数据区更新已有的数据
            .print() // 打印前10个数据

        scc.start() // 真正启动程序
        scc.awaitTermination() //阻塞等待
    }

    val updateFunc = (currentValues: Seq[Int], preValue: Option[Int]) => {
        val curr = currentValues.sum
        val pre = preValue.getOrElse(0)
        Some(curr + pre)
    }

    /**
     * 创建一个从kafka获取数据的流.
     * @param scc           spark streaming上下文
     * @param kafkaParam    kafka相关配置
     * @param topics        需要消费的topic集合
     * @return
     */
    def createStream(scc: StreamingContext, kafkaParam: Map[String, String], topics: Set[String]) = {
        KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](scc, kafkaParam, topics)
    }
}

看效果

  1. 运行spark程序

  2. 使用kafka-console-producer工具往kafka中依次写入如下数据

  3. 观察spark程序的输出结果


可以看到,只要我们往kafka中写入数据,spark程序就能实时(并不是真实时,这得看duration设置为多少,比如本例设置的是5s,那就有可能有5s的处理延迟)地统计到目前为止,各个单词出现的次数。

DirectStream和Stream的区别

从高层次的角度看,之前的和Kafka集成方案(reciever方法)使用WAL工作方式如下:

  1. 运行在Spark workers/executors上的Kafka Receivers连续不断地从Kafka中读取数据,其中用到了Kafka中高层次的消费者API。
  2. 接收到的数据被存储在Spark workers/executors中的内存,同时也被写入到WAL中。只有接收到的数据被持久化到log中,Kafka Receivers才会去更新Zookeeper中Kafka的偏移量。
  3. 接收到的数据和WAL存储位置信息被可靠地存储,如果期间出现故障,这些信息被用来从错误中恢复,并继续处理数据。

这个方法可以保证从Kafka接收的数据不被丢失。但是在失败的情况下,有些数据很有可能会被处理不止一次!这种情况在一些接收到的数据被可靠地保存到WAL中,但是还没有来得及更新Zookeeper中Kafka偏移量,系统出现故障的情况下发生。这导致数据出现不一致性:Spark Streaming知道数据被接收,但是Kafka那边认为数据还没有被接收,这样在系统恢复正常时,Kafka会再一次发送这些数据。

这种不一致产生的原因是因为两个系统无法对那些已经接收到的数据信息保存进行原子操作。为了解决这个问题,只需要一个系统来维护那些已经发送或接收的一致性视图,而且,这个系统需要拥有从失败中恢复的一切控制权利。基于这些考虑,社区决定将所有的消费偏移量信息只存储在Spark Streaming中,并且使用Kafka的低层次消费者API来从任意位置恢复数据

为了构建这个系统,新引入的Direct API采用完全不同于Receivers和WALs的处理方式。它不是启动一个Receivers来连续不断地从Kafka中接收数据并写入到WAL中,而且简单地给出每个batch区间需要读取的偏移量位置,最后,每个batch的Job被运行,那些对应偏移量的数据在Kafka中已经准备好了。这些偏移量信息也被可靠地存储(checkpoint),在从失败中恢复可以直接读取这些偏移量信息。

需要注意的是,Spark Streaming可以在失败以后重新从Kafka中读取并处理那些数据段。然而,由于仅处理一次的语义,最后重新处理的结果和没有失败处理的结果是一致的。

因此,Direct API消除了需要使用WAL和Receivers的情况,而且确保每个Kafka记录仅被接收一次并被高效地接收。这就使得我们可以将Spark Streaming和Kafka很好地整合在一起。总体来说,这些特性使得流处理管道拥有高容错性,高效性,而且很容易地被使用。

本节内容参考:http://dataunion.org/12102.html


你可能感兴趣的:(spark)