• 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

  • 相关阅读:
    2019.6.20刷题统计
    36 线程 队列 守护线程 互斥锁 死锁 可重入锁 信号量
    35 守护进程 互斥锁 IPC 共享内存 的方式 生产者消费者模型
    34 进程 pid ppid 并发与并行,阻塞与非阻塞 join函数 process对象 孤儿进程与僵尸进程
    33 udp 域名 进程
    32 粘包 文件传输
    31 socket客户端. 服务器 异常 语法
    30 网络编程
    29 元类 异常
    26 封装 反射 常用内置函数
  • 原文地址:https://www.cnblogs.com/yanshw/p/11942058.html
Copyright © 2020-2023  润新知