• spark教程(16)-Streaming 之 DStream 详解


    DStream 其实是 RDD 的序列,它的语法与 RDD 类似,分为 transformation(转换) 和 output(输出) 两种操作;

    DStream 的转换操作分为 无状态转换 和 有状态转换,且 tansformation 也是惰性的;

    DStream 的输出操作请参考 我的博客 Streaming

    无状态转换

    转换操作只作用于单个 RDD,即单个数据流的 batch;

    例如,每次根据采集到的数据流统计单词个数,第一次采集到的是  a 2个 b 1个,第二次采集到的是 a 1个 b 1个,那么第二次的输出也是 a 1 b 1,并不与前面的累计

    DStream 的转换操作最终还是会转化成 RDD 的转换操作,这个转化由 spark streaming 完成

    本文只介绍部分 API,详细请参考源码 /usr/lib/spark/python/pyspark/streaming/dstream.py

    基本转换

    为了解释下面的操作,假定输入都一样,如下

    hellp spark
    hello hadoop hive

    map(self, f, preservesPartitioning=False):生成一个新的 DStream,不解释了

    [u'hellp', u'spark']
    [u'hello', u'hadoop', u'hive']

    flatMap(self, f, preservesPartitioning=False):把一个 func 作用于 DStream 的所有元素,并将生成的结果展开

    hellp
    spark
    hello
    hadoop
    hive

    filter(self, f):不解释了

    repartition(self, numPartitions):把 DStream 中每个 RDD 进行分区,用于提高并发

    union(self, other):在一个 RDD 上再加一个 RDD

     

    聚合转换

    count(self):

    reduce(self, func):

    countByValue(self):返回一个由键值对型 RDD 构成的 DStream

    # 输入 
    a
    a
    b
    # 输出
    (u'a', 2)
    (u'b', 1)

    键值对转换

    cogroup(self, other, numPartitions=None):把两个 kv 构成的 DStream 根据 key 进行合并,注意是取 key 的并集

    sc = SparkContext()
    
    ssc = StreamingContext(sc, 30)
    line1 = ssc.socketTextStream('192.168.10.11', 9999)
    out1 = line1.map(lambda x: (len(x), x)).groupByKey()    # value 没有进行任何操作
    out1.pprint()
    
    line2 = ssc.socketTextStream('192.168.10.11', 9998)
    out2 = line2.map(lambda x: (len(x), x)).groupByKey()
    out2.pprint()
    
    out3 = out1.cogroup(out2)
    out3.pprint()
    
    ssc.start()
    ssc.awaitTermination()

    很遗憾,合并之后,value 是个对象集,不可见

    join(self, other, numPartitions=None):把两个 kv 构成的 DStream 连接起来

    如 (K,V) 构成的 DStream 和 (K,W) 构成的 DStream 连接后是 (K, (V,W))

    同样有 左连接、右连接、全连接

    out3 = out1.join(out2)      # 内连接   二者都有
    out3 = out1.leftOuterJoin(out2) # 左连接   左边有,找对应右边的
    out3 = out1.rightOuterJoin(out2)    # 右连接  右边有,找对应左边的
    out3 = out1.fullOuterJoin(out2)     # 全连接   笛卡尔内积

    reduceByKey(self, func, numPartitions=None):

    groupByKey(self, numPartitions=None):将构成 DStream 的 RDD 中的元素进行 分组 

    还有很多,不一一解释了,记住本质,这些虽然是 DStream 的 API,但是这些 API 其实是作用于 构成 DStream 的 RDD 上的 

    transform:这个比较特殊

    transform(self, func):
            """
            Return a new DStream in which each RDD is generated by applying a function
            on each RDD of this DStream.
    
            `func` can have one argument of `rdd`, or have two arguments of
            (`time`, `rdd`)
            """

    输入一个 函数,这个函数作用于 构成 DStream 的每个 RDD ,并返回新的 RDD ,重新构成一个 DStream

    transform 用于 机器学习 或者 图计算 时优势明显

    示例

    from pyspark import SparkContext
    from pyspark.streaming import StreamingContext
    
    sc = SparkContext()
    ssc = StreamingContext(sc, 20)
    line1 = ssc.socketTextStream('192.168.10.11', 9999)
    out1 = line1.map(lambda x: (len(x), x))
    ### 输入为
    # a
    # aaa
    # aa
    out1.pprint()
    # (1, u'a')
    # (3, u'aaa')
    # (2, u'aa')        这是一个 RDD,被 transform 的 func 作用
    
    out2 = out1.transform(lambda x: x.sortByKey())
    out2.pprint()
    # (1, u'a')
    # (2, u'aa')
    # (3, u'aaa')
    
    out3 = out1.transform(lambda x: x.mapValues(lambda m: ''))  
    out3.pprint()
    
    ssc.start()
    ssc.awaitTermination()

    有状态转换

    转换操作作用于之前所有的 RDD 上

    例如,每次根据采集到的数据流统计单词个数,第一次采集到的是  a 2个 b 1个,第二次采集到的是 a 1个 b 1个,那么第二次的输出也是 a 3 b 2,累计之前的

    updateStateByKey(self, updateFunc, numPartitions=None, initialRDD=None):更新当前 RDD 的状态,不好解释,用例子帮助理解

    需要设置检查点, 用于保存状态

    示例

    from pyspark import SparkContext
    from pyspark.streaming import StreamingContext
    
    def updateFunction(newValues, runningCount):
        if runningCount is None:
            runningCount = 0
        return sum(newValues, runningCount)  # add the new values with the previous running count to get the new count
    
    sc = SparkContext()
    ssc = StreamingContext(sc, 20)
    ssc.checkpoint('/usr/lib/spark')        # 必须有检查点
    
    line1 = ssc.socketTextStream('192.168.10.11', 9999)
    out1 = line1.map(lambda x: (x, 1)).reduceByKey(lambda x, y: x + y)    # 对当前 RDD 的处理
    
    runningCounts = out1.updateStateByKey(updateFunction)   # 更新状态
    runningCounts.pprint()
    
    ssc.start()
    ssc.awaitTermination()

    示例操作

    由于统计全局,所以需要checkpoint数据会占用较大的存储。而且效率也不高。所以很多时候不建议使用updateStateByKey

    窗口操作

    updateStateByKey 也是一种窗口,只是窗口大小不固定;

    这里的窗口就是指滑窗,跟 均值滤波里面的滑窗意思一样;

    这里的滑窗内的元素是 RDD;

    滑窗有 窗口尺寸 和 滑动步长 两个概念

    滑窗也是有状态的转换

    这里的 尺寸 和 步长 都是用时间来描述;

    尺寸 是 采集周期的 N 倍,步长 也是 采集周期的 N 倍

    window(self, windowDuration, slideDuration=None):windowDuration 为 窗口时长,slideDuration 为步长

    示例

    from pyspark import SparkContext
    from pyspark.streaming import StreamingContext
    
    sc = SparkContext()
    ssc = StreamingContext(sc, 20)
    line1 = ssc.socketTextStream('192.168.10.11', 9999)
    
    ## 先处理再滑窗
    out1 = line1.map(lambda x: (len(x), x))
    out2 = out1.window(40)
    # out2 = out2.groupByKey()    # 滑窗后可继续处理
    out2.pprint()
    
    ssc.start()
    ssc.awaitTermination()

    示例操作

    可以看到有重复计算的内容

    reduceByWindow(self, reduceFunc, invReduceFunc, windowDuration, slideDuration):当 slideDuration < windowDuration 时,计算是有重复的,那么我们可以不用重新获取或者计算,而是通过获取旧信息来更新新的信息,这样可以提高效率

    函数解释

    window 计算方式如下===

    win1 = time1 + time2 + time3

    win2 = time3 + time4 + time5

    显然 time3 被重复获取并计算

    reduceByWindow 计算方式如下====

    win1 = time1 + time2 + time3

    win2 = win1 + time4 + time5 -time1 -time2

    reduceFunc 是对新产生的数据(time4,time5) 进行计算;

    invReduceFunc 是对之前的旧数据(time1,time3) 进行计算;

    示例

    from pyspark import SparkContext
    from pyspark.streaming import StreamingContext
    
    def func1(x, y):
        return x+y
    
    def func2(x, y):
        return -x-y
    
    sc = SparkContext(appName="windowStream", master="local[*]")
    # 第二个参数指统计多长时间的数据
    ssc = StreamingContext(sc, 10)
    ssc.checkpoint("/tmp/window")
    lines = ssc.socketTextStream('192.168.10.11', 9999)
    # 第一个参数执行指定函数, 第二个参数是窗口长度, 第三个参数是滑动间隔
    lines = lines.flatMap(lambda x: x.split(' ')).map(lambda x: (x, 1))
    dstream = lines.reduceByWindow(func1, func2, 20, 10)
    dstream.pprint()
    
    ssc.start()
    ssc.awaitTermination()

    reduceByWindow 和 window 可实现相同效果;

    reduceByWindow 的底层是 reduceByKeyAndWindow,用法也完全相同,reduceByKeyAndWindow 的效率更高

    需要设置检查点, 用于保存状态

    reduceByKeyAndWindow(self, func, invFunc, windowDuration, slideDuration=None,
    numPartitions=None, filterFunc=None):与 reduceByWindow 的区别是 它的输入需要 kv 对

    dstream = lines.reduceByKeyAndWindow(func1, func2, 20, 10)

    countByWindow(self, windowDuration, slideDuration):计数

    需要设置检查点, 用于保存状态

    示例

    sc = SparkContext()
    ssc = StreamingContext(sc, 20)
    line1 = ssc.socketTextStream('192.168.10.11', 9999)
    
    ssc.checkpoint('/usr/lib/spark')
    
    ## 先处理再滑窗
    out1 = line1.map(lambda x: x)
    out2 = out1.countByWindow(40, 20)
    # out2 = out2.groupByKey()    # 滑窗后可继续处理
    out2.pprint()
    
    ssc.start()
    ssc.awaitTermination()

    countByValueAndWindow(self, windowDuration, slideDuration, numPartitions=None):

    groupByKeyAndWindow(self, windowDuration, slideDuration, numPartitions=None):

    不一一解释了

    总结

    1. 凡是带 key 的都需要输入 kv 对

    2. 凡是需要记录上个 状态的 都需要设置检查点

    参考资料:

    《Spark大数据分析核心概念技术及实践OCR-2017》 电子书

     https://www.cnblogs.com/libin2015/p/6841177.html

  • 相关阅读:
    windows下安装redis
    关于 tomcat 集群中 session 共享的三种方法
    利用Nginx做负载均衡
    Spring4 MVC Hibernate4集成
    <mvc:annotation-driven />到底帮我们做了啥
    Spring 开启Annotation <context:annotation-config> 和 <context:component-scan>诠释及区别
    JPA注解指南
    Hibernate与Jpa的关系
    ActiveMQ实现负载均衡+高可用部署方案
    信号接口-视频输出端口介绍
  • 原文地址:https://www.cnblogs.com/yanshw/p/11942058.html
Copyright © 2020-2023  润新知