相关内容原文地址:

SparkStreaming之解析mapWithState

Spark Streaming状态管理函数

updateStateByKey和mapWithState

SparkStreaming之mapWithState



一、状态管理函数

Spark Streaming中状态管理函数包括updateStateByKey和mapWithState,都是用来统计全局key的状态的变化的。它们以DStream中的数据进行按key做reduce操作,然后对各个批次的数据进行累加,在有新的数据信息进入或更新时。能够让用户保持想要的不论什么状。

二、mapWithState

2.1关于mapWithState

mapWithState也会统计全局的key的状态,但是如果没有数据输入,便不会返回之前的key的状态,类似于增量的感觉。

需要自己写一个匿名函数func来实现自己想要的功能。如果有初始化的值得需要,可以使用initialState(RDD)来初始化key的值。 另外,还可以指定timeout函数,该函数的作用是,如果一个key超过timeout设定的时间没有更新值,那么这个key将会失效。这个控制需要在func中实现,必须使用state.isTimingOut()来判断失效的key值。如果在失效时间之后,这个key又有新的值了,则会重新计算。如果没有使用isTimingOut,则会报错。

2.2mapWithState示例Scala:

package spark2x

import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.{DStream, MapWithStateDStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, State, StateSpec, StreamingContext} object MapWithState {
// 设置本地运行模式
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("MapWithState")
.getOrCreate() // 创建一个context,批次间隔为2秒钟,
val ssc: StreamingContext = new StreamingContext(spark.sparkContext, Seconds(3)) // 设置checkpoint目录
ssc.checkpoint(".") // 创建一个ReceiverInputDStream,从服务器端的netcat接收数据。
// 服务器主机名SC01(SC01已在Window上的hosts文件中做了映射,没做映射的则写ip就OK了),监听端口为6666
val line: ReceiverInputDStream[String] = ssc.socketTextStream("SC01", 6666) // 对接收到的数据进行处理,进行切割,分组形式为(day, 1) (word 1)
val wordsStream: DStream[(String, Int)] = line.flatMap(_.split(" ")).map((_, 1)) val wordCount: MapWithStateDStream[String, Int, Int, Any] = wordsStream.mapWithState(StateSpec.function(func).timeout(Seconds(30))) // 打印
wordCount.print()
// 提交
ssc.start()
//
ssc.awaitTermination()
} /**
* 定义一个函数,该函数有三个类型word: String, option: Option[Int], state: State[Int]
* 其中word代表统计的单词,option代表的是历史数据,state代表的是返回的状态
*/
val func = (word: String, option: Option[Int], state: State[Int]) => {
if(state.isTimingOut()){
println(word + "is timeout")
}else{
// 获取历史数据,当前值加上上一个批次的该状态的值
val sum = option.getOrElse(0) + state.getOption().getOrElse(0)
// 单词和该单词出现的频率
val wordFreq = (word, sum)
// 更新状态
state.update(sum)
wordFreq
}
}
}

mapWithState它会按照时间线在每一个批次间隔返回之前的发生改变的或者新的key的状态,不发生变化的不返回。同时mapWithState可以不用设置checkpoint,返回的数据量少,性能和效率都比mapWithState好。

2.3mapWithState算子应用示例

import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, State, StateSpec, StreamingContext} object MapWithStateApp {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("MapWithStateApp")
val ssc = new StreamingContext(conf,Seconds(5))
ssc.checkpoint("hdfs://192.168.137.251:9000/spark/data")
val lines = ssc.socketTextStream("hadoop000",9999)
val words = lines.flatMap(_.split(" "))
val pairs = words.map(x=>(x,1)).reduceByKey(_+_)
// Update the cumulative count using mapWithState
// This will give a DStream made of state (which is the cumulative count of the words)
val mappingFunc = (word: String, one: Option[Int], state: State[Int]) => {
val sum = one.getOrElse(0) + state.getOption.getOrElse(0)
val output = (word, sum)
state.update(sum)
output
} val wordcounts = pairs.mapWithState(StateSpec.function(mappingFunc))
wordcounts.print()
ssc.start()
ssc.awaitTermination()
}
}

2.4mapWithState应用示例

package stateParse

import org.apache.spark.streaming._
import org.apache.spark.{SparkConf, SparkContext} /**
* Author: shawn pross
* Date: 2018/09/10
* Description:
*/
object TestMapWithState {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName(s"${this.getClass.getSimpleName}")
conf.setMaster("local[2]")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Seconds(3))
ssc.checkpoint("/checkpoint/") val line = ssc.socketTextStream("127.0.0.1",9999)
val wordDStream = line.flatMap(_.split(",")).map(x=>(x,1)) //状态更新函数,output是输出,state是状态
val mappingFunc = (userId:String,value:Option[Int],state:State[Int])=>{
val sum= value.getOrElse(0) + state.getOption().getOrElse(0)
val output = (userId,sum)
state.update(sum)
output
} //通过mapWithState更新状态,设置状态超时时间为1小时
val stateDStream = wordDStream.mapWithState(StateSpec.function(mappingFunc).timeout(Minutes(60))).print() ssc.start()
ssc.awaitTermination()
}
}

mapWithState接收的参数是一个StateSpec对象,在StateSpec中封装了状态管理的函数。我们定义了一个状态更新函数mappingFunc,该函数会更新指定用户的状态,同时会返回更新后的状态,将该函数传给mapWithState,并设置状态超时时间。SparkStreaming通过根据我们定义的更新函数,在每个计算时间间隔内更新内部维护的状态,同时返回经过mappingFunc处理后的结果数据流。

2.5SparkStreaming之mapWithState

与updateStateByKey方法相比,使用mapWithState方法能够得到6倍的低延迟的同时维护的key状态的数量要多10倍,这一性能的提升和扩展性可以从基准测试结果得到验证,所有的结果全部在实践间隔为1秒的batch和相同大小的集群中生成。

下图比较的是mapWithState方法和updateStateByKey方法处理1秒的batch所消耗的平均时间。在本例子中,我们为同样数量的的key(0.25-1百万)保存状态,然后已同意的速率(30个更新/s)对其进行更新。可以看到mapWithState方法比updateStateByKey方法快了8倍,从而允许更低的端到端的延迟。

package com.llcc.sparkSql.MyTimeSort

import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
import org.apache.spark.streaming.StateSpec
import org.apache.spark.streaming.State object MapWithStateDemo {
def main(args: Array[String]): Unit = {
/**
* local[1] 中括号里面的数字都代表的是启动几个工作线程
* 默认情况下是一个工作线程。那么做为sparkstreaming 我们至少要开启
* 两个线程,因为其中一个线程用来接收数据,这样另外一个线程用来处理数据。
*/
val conf=new SparkConf().setMaster("local[2]").setAppName("MapWithStateDemo")
/**
* Seconds 指的是每次数据数据的时间范围 (bacth interval)
*/
val ssc=new StreamingContext(conf,Seconds(2));
ssc.checkpoint(".") val fileDS=ssc.socketTextStream("hadoop1", 9999)
val wordDstream =fileDS.flatMap { line => line.split("\t") }
.map { word => (word,1) } /**
* word: String, one: Option[Int], state: State[Int]
* 这个函数里面有三个参数
* 第一个参数:word: String 代表的就是key
* 第二个参数:one: Option[Int] 代表的就是value
* 第三个参数:state: State[Int] 代表的就是状态(历史状态,也就是上次的结果)
*
* hello,4
*
* hello,1
*
* hello,5
*/
val mappingFunc = (word: String, one: Option[Int], state: State[Int]) => {
val sum = one.getOrElse(0) + state.getOption.getOrElse(0)
val output = (word, sum)
state.update(sum)
output
} val initialRDD = ssc.sparkContext.parallelize(List(("hello", 1), ("world", 1)))
/**
* hello,1
* hello,2
* world,2
*/
val stateDstream = wordDstream.mapWithState(
StateSpec.function(mappingFunc).initialState(initialRDD)) /**
* 打印RDD里面前十个元素
*/
// wordcount.print()
stateDstream.print();
//启动应用
ssc.start()
//等待任务结束
ssc.awaitTermination()
}
}

三、updateStateByKey

3.1关于updateStateByKey

updateStateByKey可以在指定的批次间隔内返回之前的全部历史数据,包括新增的,改变的和没有改变的。由于updateStateByKey在使用的时候一定要做checkpoint,当数据量过大的时候,checkpoint会占据庞大的数据量,会影响性能,效率不高。

3.2updateStateByKey示例Scala:

package spark2x

import org.apache.spark.SparkContext
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} object UpdateStateByKeyDemo {
def main(args: Array[String]): Unit = {
/** 第一步:配置SparkConf:
* 1,至少2条线程:因为Spark Streaming应用程序在运行的时候,至少有一条
* 线程Receiver用于不断的循环接收数据,还有一条线程是Executor用于处理接受的数据(少于两条
* 就没有线程用于处理数据,窗口不会显示数据。并且随着时间的推移,内存和磁盘由于负担过重而崩溃);
* 2,对于集群而言,根据已有经验,大概5个左右的Core是性能最佳(一般分配为奇数个Core)
*/
val spark = SparkSession.builder()
.master("local[2]")
.appName("UpdateStateByKeyDemo")
.getOrCreate()
val conf: SparkContext = spark.sparkContext
/**
* 第二步:创建SparkStreamingContext:
* 1,这个是SparkStreaming应用程序所有功能的起始点和程序调度的核心
* SparkStreamingContext的构建可以基于SparkConf参数,也可基于持久化的SparkStreamingContext的内容
* 来恢复过来(典型的场景是Driver崩溃后重新启动,由于Spark Streaming具有连续7*24小时不间断运行的特征,
* 所有需要在Driver重新启动后继续上衣系的状态,此时的状态恢复需要基于曾经的Checkpoint);
* 2,在一个Spark Streaming应用程序中可以创建若干个SparkStreamingContext对象,使用下一个SparkStreamingContext
* 之前需要把前面正在运行的SparkStreamingContext对象关闭掉,由此,我们获得一个重大的启发SparkStreaming框架也只是
* Spark Core上的一个应用程序而已,只不过Spark Streaming框架箱运行的话需要Spark工程师写业务逻辑处理代码;
*/
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
//报错解决办法做checkpoint,开启checkpoint机制,把checkpoint中的数据放在这里设置的目录中,生产环境下一般放在HDFS中
ssc.checkpoint("hdfs://SC01:8020/user/tmp/cp-20181201") /**
* 第三步:创建Spark Streaming输入数据来源input Stream:
* 1,数据输入来源可以基于File、HDFS、Flume、Kafka、Socket等
* 2, 在这里我们指定数据来源于网络Socket端口,Spark Streaming连接上该端口并在运行的时候一直监听该端口
* 的数据(当然该端口服务首先必须存在),并且在后续会根据业务需要不断的有数据产生(当然对于Spark Streaming
* 应用程序的运行而言,有无数据其处理流程都是一样的);
* 3,如果经常在每间隔5秒钟没有数据的话不断的启动空的Job其实是会造成调度资源的浪费,因为并没有数据需要发生计算,所以
* 实例的企业级生成环境的代码在具体提交Job前会判断是否有数据,如果没有的话就不再提交Job;
*/
val line: ReceiverInputDStream[String] = ssc.socketTextStream("SC01", 6666) /**
* 第四步:接下来就像对于RDD编程一样基于DStream进行编程!!!原因是DStream是RDD产生的模板(或者说类),在Spark Streaming具体
* 发生计算前,其实质是把每个Batch的DStream的操作翻译成为对RDD的操作!!!
* 对初始的DStream进行Transformation级别的处理,例如map、filter等高阶函数等的编程,来进行具体的数据计算
* 进行单词拆分
*/
val words: DStream[String] = line.flatMap(_.split(" ")) /**
* 对初始的DStream进行Transformation级别的处理,例如map、filter等高阶函数等的编程,来进行具体的数据计算
* 单词分组计数实,word => (word, 1) Word ->(word, 1) day -> day(day, 1)
*/
val pairs: DStream[(String, Int)] = words.map((_, 1)) /**
* 通过updateStateByKey来以Batch Interval为单位来对历史状态进行更新,
* 这是功能上的一个非常大的改进,否则的话需要完成同样的目的,就可能需要把数据保存在Redis、
* Tagyon或者HDFS或者HBase或者数据库中来不断的完成同样一个key的State更新,如果你对性能有极为苛刻的要求,
* 且数据量特别大的话,可以考虑把数据放在分布式的Redis或者Tachyon内存文件系统中;
* Spark2.X后mapWithState应该非常稳定了。
*/
val wordCount: DStream[(String, Int)] = pairs.updateStateByKey((values: Seq[Int], state: Option[Int]) => {
var newValue = state.getOrElse(0)
for (value <- values) {
newValue += value
}
Option(newValue)
}) /**
* 此处的print并不会直接出发Job的执行,因为现在的一切都是在Spark Streaming框架的控制之下的,对于Spark Streaming
* 是否触发真正的Job运行是基于设置的Duration时间间隔的
* 需要注意的是Spark Streaming应用程序要想执行具体的Job,对Dtream就必须有output Stream操作,
* output Stream有很多类型的函数触发,类print、saveAsTextFile、saveAsHadoopFiles等,最为重要的一个
* 方法是foraeachRDD,因为Spark Streaming处理的结果一般都会放在Redis、DB、DashBoard等上面,foreachRDD
* 主要就是用用来完成这些功能的,而且可以随意的自定义具体数据到底放在哪里!!!
*/
wordCount.print() /**
* Spark Streaming执行引擎也就是Driver开始运行,Driver启动的时候是位于一条新的线程中的,当然其内部有消息循环体,用于
* 接受应用程序本身或者Executor中的消息;
*/
// 开始提交任务
ssc.start()
// 线程等待,等待处理下一批次任务
ssc.awaitTermination()
} /** Iterator[(K, Seq[V], Option[S])]) => Iterator[(K, S)]
* 在调用updateStateByKey中,需要传入一个用于计算历史批次和当前批次数据的函数
* 该函数中有几个类型:String, Seq[Int], Option[Int])]
* String代表元组中每一个单词,也就是key
* Seq[Int]代表当前批次相同key对应的value,比如Seq(1,1,1,1)
* Option[Int]代表上一批次中相同key对应的累加的结果,有可能有值,有可能没有值。
* 此时,获取历史批次的数据时,最好用getOrElse方法
*/
val func = (it: Iterator[(String, Seq[Int], Option[Int])]) => {
it.map(tup => {
(tup._1, tup._2.sum + tup._3.getOrElse(0))
})
}
}

四、updateStateByKey和mapWithState的区别

updateStateByKey可以在指定的批次间隔内返回之前的全部历史数据,包括新增的,改变的和没有改变的。由于updateStateByKey在使用的时候一定要做checkpoint,当数据量过大的时候,checkpoint会占据庞大的数据量,会影响性能,效率不高。

mapWithState只返回变化后的key的值,这样做的好处是,我们可以只是关心那些已经发生的变化的key,对于没有数据输入,则不会返回那些没有变化的key的数据。这样的话,即使数据量很大,checkpoint也不会像updateStateByKey那样,占用太多的存储,效率比较高(再生产环境中建议使用这个)。

4.1适用场景

updateStateByKey可以用来统计历史数据。例如统计不同时间段用户平均消费金额,消费次数,消费总额,网站的不同时间段的访问量等指标

mapWithState可以用于一些实时性较高,延迟较少的一些场景,例如你在某宝上下单买了个东西,付款之后返回你账户里的余额信息。

Spark Streaming状态管理函数updateStateByKey和mapWithState的更多相关文章

  1. Spark流式状态管理(updateStateByKey、mapWithState等)

    通常使用Spark的流式框架如Spark Streaming,做无状态的流式计算是非常方便的,仅需处理每个批次时间间隔内的数据即可,不需要关注之前的数据,这是建立在业务需求对批次之间的数据没有联系的基 ...

  2. 14:Spark Streaming源码解读之State管理之updateStateByKey和mapWithState解密

    首先简单解释一下)) //要使用updateStateByKey方法,必须设置Checkpoint. ssc.checkpoint("/checkpoint/") val sock ...

  3. Spark Streaming源码解读之State管理之UpdataStateByKey和MapWithState解密

    本期内容 : UpdateStateByKey解密 MapWithState解密 Spark Streaming是实现State状态管理因素: 01. Spark Streaming是按照整个Bach ...

  4. Spark Streaming实战

    1.Storm 和 SparkStreaming区别 Storm                      纯实时的流式处理,来一条数据就立即进行处理 SparkStreaming 微批处理,每次处理 ...

  5. 9. Spark Streaming技术内幕 : Receiver在Driver的精妙实现全生命周期彻底研究和思考

        原创文章,转载请注明:转载自 听风居士博客(http://www.cnblogs.com/zhouyf/)       Spark streaming 程序需要不断接收新数据,然后进行业务逻辑 ...

  6. Spark Streaming揭秘 Day14 State状态管理

    Spark Streaming揭秘 Day14 State状态管理 今天让我们进入下SparkStreaming的一个非常好用的功能,也就State相关的操作.State是SparkStreaming ...

  7. Spark Streaming之窗口函数和状态转换函数

    流处理主要有3种应用场景:无状态操作.window操作.状态操作. reduceByKeyAndWindow import kafka.serializer.StringDecoder import ...

  8. Spark Streaming updateStateByKey和mapWithState源码解密

    本篇从二个方面进行源码分析: 一.updateStateByKey解密 二.mapWithState解密 通过对Spark研究角度来研究jvm.分布式.图计算.架构设计.软件工程思想,可以学到很多东西 ...

  9. 55、Spark Streaming:updateStateByKey以及基于缓存的实时wordcount程序

    一.updateStateByKey 1.概述 SparkStreaming 7*24 小时不间断的运行,有时需要管理一些状态,比如wordCount,每个batch的数据不是独立的而是需要累加的,这 ...

随机推荐

  1. File类的特点?如何创建File类对象?Java中如何操作文件内容,什么是Io流Io流如何读取和写入文件?字节缓冲流使用原则?

    重难点提示 学习目标 1.能够了解File类的特点(存在的意义,构造方法,常见方法) 2.能够了解什么是IO流以及分类(IO流的概述以及分类) 3.能够掌握字节输出流的使用(继承体系结构介绍以及常见的 ...

  2. Pytest测试框架(二):pytest 的setup/teardown方法

    PyTest支持xUnit style 结构, setup() 和 teardown() 方法用于初始化和清理测试环境,可以保证测试用例的独立性.pytest的setup/teardown方法包括:模 ...

  3. Linux 网络排错检查思路

    Linux 网络排错检查思路 graph TD A[当网络不通时] --> B{ping想要访问的地址,<br>如www.runoob.com} B --> |不通| C{pi ...

  4. Istio 知多少 | 下一代微服务的守护者

    1. 引言 在写完eShopOnContainers 知多少[12]:Envoy gateways后,就一直想进一步探索Service Mesh,最近刚在极客时间上学完<Service Mesh ...

  5. 【C++】《C++ Primer 》第十四章

    第十四章 重载运算与类型转换 一.基本概念 重载运算符是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成.也包含返回类型.参数列表以及函数体. 当一个重载的运算符是成员函数时 ...

  6. LeetCode232 用栈实现队列

    使用栈实现队列的下列操作: push(x) -- 将一个元素放入队列的尾部. pop() -- 从队列首部移除元素. peek() -- 返回队列首部的元素. empty() -- 返回队列是否为空. ...

  7. Android事件分发机制一:事件是如何到达activity的?

    事件分发,真的一定从Activity开始吗? 前言 很高兴遇见你~ 事件分发,android中一个老生常谈的话题了.基本的流程我们也都知道是从Activity开始分发,但有一个关键问题是:事件是如何到 ...

  8. 如何实现一个简易版的 Spring - 如何实现 Constructor 注入

    前言 本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简单 Setter 注入,这篇来看看要如何去实现一个简单的 Constructor 注入功 ...

  9. ctfhub技能树—web前置技能—http协议—Cookie

    打开靶机环境 查看显示内容 根据提示,需要admin登录才能得到flag 题目介绍为Cookie欺骗.认证.伪造 介绍一下cookie和session 一.cookie: 在网站中,http请求是无状 ...

  10. (四)React Ant Design Pro + .Net5 WebApi:PostgreSQL数据库环境搭建

    一.简介 PostgreSQL,开源数据库(没听过小伙伴自己反思一下自行百度) PgAdmin,官方提供的数据库管理工具. 二.环境 1. 官网下载包,安装数据库 tar xjvf /app/pack ...