13.spark streaming之快速入门

简介

  Spark Streaming是Spark核心API的扩展,可以实现可伸缩、高吞吐量、具备容错机制的实时流时数据的处理。支持多种数据源,比如Kafka、Flume、Twitter、ZeroMQ、Kinesis 以及TCP sockets。

  可以使用诸如map、reduce、join和window等高级函数进行复杂算法(比如,机器学习和图计算)的处理。最后还可以将处理结果存储到文件系统,数据库和仪表盘。

spark streaming.png

架构与抽象

抽象

  Spark Streaming接收实时流的数据,并根据一定的时间间隔拆分成一批批的数据,然后通过Spark Engine处理这些批数据,最终得到处理后的一批批结果数据。

streaming-flow.png

  Spark Streaming提供了一个叫做DStream(discretized stream,离散流)的抽象概念,DStream由一系列的RDD组成,表示每个批次中连续的数据流。DStream可以从输入源(比如,Kafka、Flume、Kinesis等)中创建,也可以从其他DStream中使用高级算子操作转换生成。

streaming-dstream.png

  DStream的所有操作其实都是对DStream中所有RDD的操作。比如,在单词统计案例中,flatMap转化操作会应用到每个行RDD上来生成单词RDD。

streaming-dstream-ops.png

架构

架构.jpg
  • Receiver:Spark Streaming内置的数据流接收器或自定义接收器,用于从数据源接收源源不断的数据流。

  • CurrentBuffer:用于缓存输入流接收器接收的数据流。

  • BlockIntervalTimer:一个定时器,用于将CurrentBuffer中缓存的数据流封装为Block后放入blocksForPushing队列中。

  • BlocksForPushing:待处理的Block

  • BlockPushingThread:此线程每隔100毫秒从BlocksForPushing队列中取出一个Block存入存储系统,并缓存到ReceivedBlockQueue队列中。

  • Block Batch:Block批次,按照批次时间间隔,从ReceivedBlockQueue队列中获取一批Block。

  • JobGenerator:Job生成器,用于给每一批Block生成一个Job。

DStream 转化操作

  DStream转化操作分为无状态(stateless)和有状态(stateful)两种。

  • 无状态转化操作中,每个批次的处理不依赖于之前批次的数据。

  • 有状态转化操作需要使用之前批次的数据或中间结果来计算当前批次的数据。

无状态转化操作

  无状态转化操作就是把简单的RDD转化操作应用到每个批次上,转化DStream中的每个RDD。

常用的无状态转化操作

函数名称 作用 scala示例
map() 对DStream中的每个元素应用指定函数,返回由各元素输出的元素组成的DStream ds.map(x => x+1)
flatMap() 对DStream中的每个元素应用指定函数,返回由各元素输出的迭代器组成的DStream ds.flatMap(x => x.split(" "))
filter 返回由给定DStream中通过筛选的元素组成的DStream ds.filter(x => x!=1)
repartition() 改变DStream的分区数 ds.repartition(10)
reduceByKey 将每个批次中键相同的记录聚合 ds.reduceByKey((x,y) => x+y)
groupByKey 将每个批次中的记录根据键分组 ds.groupByKey()
  • 使用map()和reduceByKey()在每个时间区间中对日志根据IP地址进行计数。

    • scala
    //假设ApacheAccessingLog是用来从Apache日志中解析条目的工具类
    val accessLogDStream = logData.map(line => ApacheAccessingLog.parseFromLogLine(line))
    val ipDStream = accessLogsDStream.map(entry => (entry.getIpAddress(), 1)
    val ipCountsDStream = ipDStream.reduceByKey((x,y) => x+y)
    
    • java
```
//假设ApacheAccessingLog是用来从Apache日志中解析条目的工具类
static final class IpTuple implements PairFunction<ApacheAccessLog, String, Long> {
    public Tuple2<String, Long> call(ApacheAccessLog log) {
        return new Tuple2<>(log.getIpAddress(), 1L);
    }
}

JavaDStream<ApacheAccessLog> accessLogDStream = logData.map(new ParseFromLogLine());
JavaPairDStream<String, Long> ipDStream = accessLogDStream.mapToPair(new IpTuple());
JavaPairDStream(String, Long) ipCountsDStream = ipDStream.reduceByKey(new LongSumReducer());
```
  • 以IP地址为键,将请求计数的数据和传输数据量的数据连接起来

    • scala
    val ipBytesDStream = accessLogsDStream.map(entry => (entry.getIpAddress(), entry.getContentSize()))
    val ipBytesSumDStream = ipBytesDStream.reduceByKey((x,y) => x+y)
    val ipBytesRequestCountDStream = ipCountsDStream.join(ipBytesSumDStream)
    
    • java
```
JavaPairDStream<String, Long> ipBytesDStream = accessLogsDStream.mapToPair(new IpContentTuple());
JavaPairDStream<String, Long> ipBytesSumDStream = ipBytesDStream.reduceByKey(new LongSumReducer());
JavaPairDStream<String, Tuple2<Long,Long>> ipBytesRequestCountDStream = ipCountsDStream.join(ipBytesSumDStream);
```
  • 使用transform()操作实现自定义转化操作,从日志记录中提取异常值。

    • scala
    val outlierDStream = accessLogsDStream.transform{
        rdd => extractOutliers(rdd)
    }
    
    • java
    JavaPairDStream<String, Long> ipRawDStream = accessLogsDStream.transform(
        new Function<JavaPairRDD<ApacheAccessLog>, JavaRDD<ApacheAccessLog>>() {
            public JavaPairRDD<ApacheAccessLog> call(JavaRDD<ApacheAccessLog> rdd) {
                return extractOutliers(rdd);
            }
        }
    );
    

有状态转化操作

  DStream的有状态转化操作是跨时间区间跟踪数据的操作,先前批次的数据也被用来在新的批次中计算结果。

  有状态转化操作主要有两种类型:滑动窗口和updateStateByKey()。前者以一个时间阶段为滑动窗口进行操作,后者用来跟踪每个键的状态变化。

设置检查点

  有状态转化操作需要在StreamingContext中打开检查点机制确保容错性。

ssc.checkpoint("hdfs://...")

基于窗口的转化操作

简介

  基于窗口的操作会在一个比StreamingContext批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。

  基于窗口的转化操作需要两个参数,分别是窗口时长和滑动时长。两者都是批次间隔的整数倍。

  • 窗口时长:控制每次计算最近的windowDuration/batchInterval个批次的数据。

  • 滑动步长:默认值与批次间隔相等。用来控制对新DStream进行计算的时间间隔。

简单案例
  • 使用window()对窗口进行计数

    • scala
    val accessLogsWindow = accessLogsDStream.window(Seconds(30), Seconds(10))
    val windowCounts = accessLogsWindow.count()
    
    • java
```
JavaDStream<ApacheAccessLog> accessLogsWindow = accessLogsDStream.window(Durations.seconds(30), Duration.seconds(10));
JavaDStream<Integer> windowCounts = accessLogsWindow.count();
```
  • 使用reduceByKeyAndWindow对每个IP地址的访问量计数

    • scala
    val ipDStream = accessLogsDStream.map(logEntry => (logEntry.getIpAddress(), 1))
    val ipCountDStream = ipDStream.reduceByKeyAndWindow(
        {(x,y) => x+y}, //加入新进入窗口的批次中的元素
        {(x,y) => x-y}, //移除离开窗口的老批次中的元素
        Seconds(30), //窗口时长
        Seconds(10) //滑动步长
    )
    
    • java
```
class ExtractIp extends PairFunction<ApacheAccessLog, String, Long> {
    public Tuple2<String, Long> call(ApacheAccessLog entry) {
        return new Tuple2(entry.getIpAddress(), 1L);
    }
}

class AddLongs extends Function2<Long, Long, Long>() {
    public Long call(Long v1, Long v2) {
        return v1 + v2;
    }
}

class SubtractLongs extends Function2<Long, Long, Long>() {
    public Long call(Long v1, Long v2) {
        return v1 - v2;
    }
}

JavaPairDStream<String, Long> ipAddressPairDStream = accessLogsDStream.mapToPair(new ExtractIp());
JavaPairDStream<String, Long> ipCountDStream = ipAddressPairDStream.reduceByKeyAndWindow(
    new AddLongs(), //加上新进入窗口的批次中的元素
    new SubtractLongs(), //移除离开窗口的老批次中的元素
    Durations.seconds(30), //窗口时长
    Durations.seconds(10) //滑动步长
)
```
  • 使用countByWindow和countByValueAndWindow对窗口计数

    • scala
    val ipDStream = accessLogsDStream.map{entry => entry.getIpAddress()}
    val ipAddressRequestCount = ipDStream.countByValueAndWindow(Seconds(30), Seconds(10))
    val requestCount = accessLogsDStream.countByWindow(Seconds(30), Seconds(10))
    
    • java
```
JavaDStream<String> ip = accessLogsDStream.map(new Function<ApacheAccessLog, String>() {
   public String call(ApacheAccessLog entry) {
        return entry.getIpAddress();
   }
});

JavaDStream<Long> requestCount = accessLogsDStream.countByWindow(Dirations.seconds(30), Durations.seconds(10));
JavaPairDStream<String, Long> ipAddressRequestCount = ip.countByValueAndWindow(Dirations.seconds(30), Durations.seconds(10));
```

updateStateByKey转化操作

简介

  updateStateByKey提供了跨批次维护状态的功能,用于键值对形式的DStream。

  updateStateByKey提供了一个update(events, oldState)函数,接收与某键相关的事件及该键之前对应的状态,返回该键对应的新状态。

  • events:当前批次中收到的事件列表
  • oldState:一个可选的状态对象,存放在Option内;如果一个键没有之前的状态,这个值为空。
  • newState:由函数返回,也以Option形式存在;可以返回一个空的Option表示删除该状态。
简单案例

  使用updateStateByKey()跟踪日志消息中各HTTP响应代码的计数。

  • scala
def updateRunningSum(values: Seq[Long], state: Option[Long]) = {
    Some(state.getOrElse(0L) + values.size)
}

val responseCodeDStream = accessLogsDStream.map(log => (log.getResponseCode(), 1L))
val responseCodeCountDStream = responseCodeDStream.updateStateByKey(updateRunningSum _)
  • java
class UpdateRunningSum implements Function2<List<Long>, Optional<Long>, Optional<Long>> {
    public Optional<Long> call(List<Long> nums, Optional<Long> current) {
        long sum = current.or(0L);
        return Optional.of(sum + nums.size());
    }
};

JavaPairDStream<Integer, Long> responseCodeCountDStream = accessLogsDStream.mapToPair(
    new PairFunction<ApacheAccessLog, Integer, Long>() {
        public Tuple2<Integer, Long> call(ApacheAccessLog log) {
            return new Tuple2(log.getResponseCode(), 1L);
        }
    }
).updateStateByKey(new UpdateRunningSum());

DStream 行动操作

  DStream行动操作同RDD的行动操作。比如,将DStream保存为SequenceFile文件。

  • scala
val writableIpAddressRequestCount = ipAddressRequestCount.map{
    (ip, count) => <new Text(ip), new LongWritable(count))
}

writableIpAddressRequestCount.saveAsHadoopFiles[SequenceFileOutputFormat[Text, LongWritable]]("outputDir", "txt")
}
  • java
JavaPairDStream<Text, LongWritable> writableDStream = ipDStream.mapToPair(
    new PairFunction<Tuple2<String, Long>, Text, LongWritable>() {
        public Tuple2<Text, LongWritable> call(Tuple2<String, Long> e) {
            return new Tuple2(new Text(e._1()), new LongWritable(e._2()));
        }
    }
);

writableDStream.saveAsHadoopFiles("outputDir", "txt", Text.class, LongWritable.class, SequenceFileOutputFormat.class);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容