Java面试——Spark原理及应用(二)

Spark原理及应用

      • [2.3、Spark Streaming的使用](#2.3、Spark Streaming的使用)
      • [2.4、Spark SQL、DataFrame、DataSet的使用](#2.4、Spark SQL、DataFrame、DataSet的使用)
      • [2.5、Spark Structured Streaming的使用](#2.5、Spark Structured Streaming的使用)
        • [2.5.1、Spark Structured Streaming简介](#2.5.1、Spark Structured Streaming简介)
        • [2.5.2、Spark Structured Streaming的数据模型](#2.5.2、Spark Structured Streaming的数据模型)
        • [2.5.3、创建一个Spark Structured Streaming应用](#2.5.3、创建一个Spark Structured Streaming应用)

2.3、Spark Streaming的使用

2.3.1、Spark Streaming的介绍

Spark Streaming基于Spark API的流式计算扩展,它实现了一个高吞吐量、高容错的流式计算引擎。SparkStreaming从多种数据源获取数据(如Kafka、Flume、Kinesis、TCP等)​,在获取数据后使用高级函数(如map、reduce、join、window)组成的计算逻辑单元进行数据处理,最终将处理后的数据实时推送到消息服务、文件系统、数据库等。除了基本的流式计算,应用程序还可以在数据流上应用Spark的机器学习和图形处理算法。Spark Streaming的流程如图所示。

Spark Streaming接收实时数据流并将数据分成很小的Batch,然后将这些Batch交给Spark Engine以微批量的形式进行实时处理,生成最终结果流。Spark Streaming的处理过程如图所示。

在API层面,Spark Streaming将实时数据流封装为DStream(Discretized Stream)的高级抽象以表示连续的数据流。DStream可以从Kafka、Flume和Kinesis等实时数据流创建,也可以通过在其他DStream上应用高级操作来创建。在内部,DStream表示为一系列RDD。SparkStreaming内部流计算的过程即Spark RDD的转化过程。

2.3.2、创建一个Spark Streaming应用

在之前的Spark项目基础上创建Spark Streaming项目,具体步骤如下。

(1)添加Maven依赖。

打开pom.xml文件,并将Spark Streaming依赖加入项目中,具体代码如下。

** (2)新建Streaming类。**

在项目的Java目录下新建一个名为Streaming的Java类,并在类中输入以下代码构建一个简单的Spark Streaming程序。

上述代码定义了一个名为NetworkWordCount的SparkStreaming计算引擎,该程序实时监听Localhost的9999端口。当有数据输入时,Spark Streaming实时接收9999端口上的数据并计算词频,最终将结果输出。在SparkStreaming应用的创建过程中包括以下核心步骤。

  • ①定义JavaStreamingContext:定义JavaStreamingContext需要传入SparkConf集群配置信息,在创建的时候可以定义Stream的持续时间,即SparkStreaming多久运算一次实时流数据,这里设置为1s,一般意义上可理解为数据延迟1s计算。具体代码如下。
  • ②定义实时数据源:Spark Streaming中的数据源可以是TCP端口上的实时数据、Kafka中的数据、文件目录中的数据等,通过调用JavaStreamingContext的转换函数将 其转换为Spark的实时数据流,为后续流计算提供数据源。数据源通过JavaStreamingContext定义,如下代码通过创建一个名为lines的JavaReceiverInputDStream表示一个监听了Localhost的9999端口的TCP数据源。
  • ③定义DStream转换操作:DStream表示从数据源接收的实时数据流。如下代码中实时流为9999端口上用户输入的每一行文本。首先通过调用flatMap操作按照空格将每行文档分割为单词,该过程被称为数据源到JavaDStream的转换。然后调用words.mapToPair()将DStream进一步转化为键值对的DStream。接着通过pairs.reduceByKey()获得每批数据中的单词频率。最后通过wordCounts.print()方法打印每秒计算的词频计数。具体代码如下。
  • ④定义DStream结果输出:上述代码通过wordCounts.print()方法将结果打印,当然也可以将计算结果存储在数据库中,发送到消息队列,或者存储到HDFS等文件系统中。
  • ⑤启动Spark Streaming:Spark Streaming的启动通过调用JavaStreamingContext的start()方法实现。

(3)启动TCP服务端。

在Linux上通过Netcat启动一个TCP服务端,具体代码如下。

(4)打包和作业提交。

在项目的根目录下输入如下命令对项目进行打包,打包后的程序在Target目录下。

Spark Streaming的作业提交与一般的作业提交类似,具体代码如下。

在作业提交后,通过http://ip:4040/jobs/可以查看启动的作业,如图所示。

在Netcat窗口输入"hello java hello spark"​,查看Spark日志,看到有以下统计结果输出。

2.3.3、DStream和RDD的关系

DStream是Spark Streaming提供的基本抽象,它表示连续的数据流,可以是从其他数据源接收的输入数据流,也可以是通过转换输入流生成的。在Spark内部,DStream由一系列连续的RDD表示,这是Spark对不可变分布式数据集的抽象。应用于DStream的任何操作都转换为底层RDD上的操作。这些底层RDD转换由Spark计算引擎完成。DStream操作隐藏了大部分细节,并为开发人员提供了更高级别的API以方便使用。DStream的计算流程和RDD的转换关系如图所示。

2.3.4、Spark Streaming的数据源

Spark Streaming提供如下两种内置流式数据的数据源。

  • 基本来源:StreamingContext API中直接提供的源,如文件系统和Socket连接等。
  • 高级资源:Kafka、Flume、Kinesis等数据源,可通过扩展实现其他依赖。

如果要在应用程序中并行接收多个数据流,则可以通过定义多个Receiver来实现,这些接收器将同时接收多个数据源上的数据。需要注意的是,分配给Spark Streaming应用程序的核心数必须大于接收器的数量,因为系统在接收数据的同时,需要有足够的线程资源来处理数据。

  • (1)常用的基本数据源。
    • ①Socket:Spark启动一个常驻内存的线程来实时监听Socket上的数据变化。具体通过如下方法创建DStream。
    • ②HDFS文件流:从与HDFS API兼容的任何文件系统(HDFS、S3、NFS)上读取数据。具体通过如下方法创建DStream。
    • ③简单文件流:从简单的文件目录上读取数据。
  • (2)常用的高级数据源。
    • ①Flume:利用Flume Spark Streaming从Flume中获取数据。
    • ②Kafka:利用Kafka Spark Streaming从Kafka中获取数据。
    • ③Kinesis:利用Kinesis Spark Streaming从Kinesis中获取数据。
2.3.5、DStream的操作

Dstream的操作可以分为普通转换操作、窗口转换操作和输出操作等。

  • (1)常用的普通转换操作。
    • ①map(func):原DStream的每个元素都通过func函数返回一个新的DStream。
    • ②flatMap(func):与map操作类似,不同的是每个输入元素都可以被映射出0个或多个输出元素。
    • ③filter(func):在原DStream上过滤出func函数返回仅为true的DStream。
    • ④repartition(numPartitions):设置DStream的分区大小。
    • ⑤union(otherStream):将2个DStream合并。
    • ⑥count():对原DStream内部所含有的RDD的元素数量进行统计。
    • ⑦reduce(func):使用函数func将原DStream中每个RDD的元素都进行聚合操作。
    • ⑧countByValue():计算DStream中每个RDD内的元素出现的频次。
    • ⑨reduceByKey(func,numTasks):根据DStream的Key进行聚合操作。
    • ⑩join(otherStream,numTasks):当被调用的类型分别为<Key,Value1>和<Key,Value2>键值对的2个DStream时,返回类型为<Key,<Value1,Value2>>键值对的一个新DStream。
    • ⑪cogroup(otherStream,numTasks):当被调用的两个DStream分别含有<Key,Value1>和<Key,Value2>键值对时,返回一个(Key,SeqValue1,SeqValue2)类型的新DStream。
  • (2)窗口转换操作。
    要理解Spark的窗口转换操作,首先要理解批处理间隔、窗口间隔、滑动间隔等基础概念。
    • ①批处理间隔:Spark Streaming中的数据处理是按批进行的,而数据采集是实时逐条进行的。Spark Streaming内部设置了批处理间隔(Batch Duration),Spark会定时把该批处理间隔内接收的数据汇总起来,新组成一个微批数据(Batch Data)并提交到Spark处理引擎进行处理。
    • ②窗口间隔:窗口间隔指窗口的持续时间,在窗口操作中只有窗口内的数据长度满足条件时才会触发批数据的处理。
    • ③滑动间隔:滑动间隔(Slide Duration)指经过多长时间窗口滑动一次形成新的窗口,这里必须注意的是,滑动间隔和窗口间隔的大小一定要设置为批处理间隔的整数倍。

批处理间隔、窗口间隔、滑动间隔的关系如图10-13所示。批处理间隔是1个时间单位,窗口间隔是3个时间单位,滑动间隔是2个时间单位。只有当窗口间隔满足条件时,DStream才触发数据处理。

常用的窗口转换操作如下。

  • ①window(windowLength,slideInterval)​:返回一个基于原DStream的窗口批次计算后得到的新DStream。

  • ②countByWindow(windowLength,slideInterval)​:返回滑动窗口内DStream中元素的数量。

  • ③reduceByWindow(func,windowLength,slideInterval)​:基于滑动窗口对原DStream中的元素进行聚合操作,得到一个新DStream。

  • ④reduceByKeyAndWindow(func,windowLength,slideInterval,​numTasks​)​:基于滑动窗口键值对类型的DStream中的值,按Key使用聚合函数func进行聚合操作,得到一个新DStream。

  • ⑤reduceByKeyAndWindow(func,invFunc,windowLength,slideInterval,​numTasks​)​:一个更高效的reduceByKeyAndWindow(​)的实现版本,先对滑动窗口中新的时间间隔内的数据增量聚合,并去除最早的与新增数据量的时间间隔内的数据统计量。例如,计算T+4时刻过去5s窗口的WordCount,那么可以将T+3时刻过去5s的统计量加上T+3,T+4的统计量,再减去T-2,T-1的统计量,这种方法可以复用中间3s的统计量,提高统计的效率。

  • ⑥countByValueAndWindow(windowLength,slideInterval,​numTasks​)​:基于滑动窗口计算原DStream中每个RDD内每个元素出现的频次并返回DStream​(Key,Long)​​。其中,Key是RDD中元素的类型,Long是元素频次。与countByValue一样,reduce任务的并发数可以通过一个可选参数进行配置。

  • (3)输出操作。

    Spark Streaming在计算完成后可使用DStream将计算结果输出到外部系统,如数据库或文件系统,常用的方法如下。

    • ①print():在Driver中打印出DStream中数据的前10个元素。
    • ②saveAsTextFiles(prefix,suffix):将DStream中的内容以文本的形式保存为文本文件,其中每次批处理间隔内产生的文件都以prefix-TIME_IN_MS.suffix的方式命名。
    • ③saveAsObjectFiles(prefix,suffix):将DStream中的内容序列化并且以SequenceFile的格式保存,其中每次批处理间隔内产生的文件都以prefix-TIME_IN_MS.suffix的方式命名。
    • ④saveAsHadoopFiles(prefix,suffix):将DStream中的内容以文本的形式保存为Hadoop文件,其中每次批处理间隔内产生的文件都以prefix-TIME_IN_MS.suffix的方式命名。
    • ⑤foreachRDD(func):将func函数应用于DStream的RDD上,这个操作会把数据输出到外部系统,比如保存RDD到文件或者数据库等。需要注意的是,func函数是在运行该Streaming应用的Driver进程中被执行的。
2.3.6、DStream的数据持久化

与RDD一样,DStream也能通过persist(​)将数据流缓存在内存中,默认持久化方式是MEMORY_ONLY_SER,也就是以序列化的方式将数据存放在内存中,这样做的好处是遇到需要多次迭代计算的程序时,可以直接使用内存中的数据,速度优势十分明显。

对于一些基于窗口的操作,如reduceByWindow、reduceByKeyAndWindow,以及基于状态的操作,如updateStateBykey,默认持久化策略为保存在内存中。

对于来自外部的数据源(Kafka、Flume、Sockets等)​,默认持久化策略是将数据副本保存在其他两台机器上。

另外,对于窗口和有状态的操作,Spark应用程序必须配置CheckPoint,通过StreamingContext来设置CheckPoint目录,通过DStream设置CheckPoint间隔时间,间隔必须是滑动间隔的倍数。

2.3.7、Spark Streaming的性能优化

Spark Streaming的性能优化主要包括优化运行时间和优化内存两方面。

  • 1)优化运行时间。
    • (1)增加并行度:确保使用整个集群的资源,而不是把任务集中在几个特定的节点上。对于包含Shuffle的操作,增加其并行度以确保更充分地使用集群资源。
    • (2)减少数据序列化、反序列化的负担:SparkStreaming默认将接收的数据序列化后存储,以减少内存的使用。但是序列化和反序列化需要更多CPU时间,因此更加高效的序列化方式(Kryo)和自定义的序列化接口可以更高效地使用CPU。
    • (3)设置合理的批处理间隔:在Spark Streaming中,Job之间存在依赖关系,后面的Job必须确保前面的Job 执行结束后才能提交。若前面的Job执行的时间超出了批处理间隔,那么后面的Job将无法按时提交,这样就会进一步延迟接下来的Job执行,造成后续Job的阻塞。因此需要设置一个合理的批处理间隔以确保Job能够在这个批处理间隔内结束批处理计算。
    • (4)减少因任务提交和分发带来的负担:通常情况下,Akka框架能够高效地确保任务及时分发,但是当批处理间隔非常小(500ms)时,提交和分发任务的延迟就变得不可接受。使用Standalone和Coarse-grained Mesos模式通常会比使用Fine-grained Mesos模式有更小的延迟。
  • 2)优化内存使用。
    • (1)控制批处理间隔内的数据量:Spark Streaming会把批处理间隔内接收的所有数据存放在Spark内部的可用内存区域中,因此必须确保当前节点Spark的可用内存至少能容纳批处理间隔内的所有数据,否则必须增加新的内存资源以提高集群的处理能力。
    • (2)及时清理不再使用的数据:Spark Streaming会将接收的数据全部存储到内存区域中,因此对于处理过后就不再需要的数据应及时清理,以确保Spark Streaming有更多的可用内存空间。通过设置合理的spark.cleaner.ttl时长来及时清理超时的无用内存数据,这个参数需要小心设置以免后续操作中所需要的数据被当作超时数据清理掉。
    • (3)调整GC策略:GC会影响Job的正常运行,延长Job的执行时间,引起一系列不可预料的问题。观察GC的运行情况,采用不同GC策略以进一步减小内存回收对Job运行的影响。

2.4、Spark SQL、DataFrame、DataSet的使用

2.4.1、Spark SQL简介

Spark SQL是用于结构化数据处理的Spark模块。它提供了两个编程抽象,分别叫作DataFrame和DataSet,它们均用于分布式SQL查询引擎。在外部,Spark SQL会将无论是DataFrame还是DataSet定义的数据都抽象为结构化数据,这样使得开发人员可以像使用简单的SQL语句一样在Spark上轻松完成复杂的大数据交互操作。在内部,Spark SQL将SQL执行操作转换成RDD,然后提交到集群执行。

2.4.2、Spark SQL查询语句

Spark SQL最简单的用法是直接执行SQL查询语句,可以使用最基本的SQL语法,也可以选择HiveSQL语法。Spark SQL可以从已有的Hive中读取数据,如果用其他编程语言运行SQL,则Spark SQL将以DataFrame的形式返回结果。

2.4.3、DataFrame

DataFrame是一种分布式数据集合,每一条数据都由多个字段组成。从概念上来说,它与关系型数据库的表或者R语言和Python中的DataFrame等价,只不过在底层,DataFrame采取了更多优化。

DataFrame可以从很多数据源加载数据,如结构化数据文件、Hive表、关系型数据库、NoSQL数据库或者已有的RDD。

2.4.4、DataSet

DataSet的目的是把RDD的优势(强类型,可以使用lambda表达式函数处理数据)和Spark SQL的优化执行引擎的优势结合到一起。DataSet由Java对象构建,在DataSet上可以使用各种Transformation算子(如map、flatMap、filter等)来完成数据的交互式计算。

2.4.5、创建一个Spark SQL应用

在上述Spark项目的基础上创建Spark SQL项目,具体步骤如下。

(1)添加Maven依赖。

打开pom.xml文件,并将Spark SQL依赖加入项目,具体代码如下。

(2)新建SQLSimple类。

在项目的Java目录下新建一个名为SQLSimple的Java类,并在类中输入下面代码构建一个简单的Spark SQL程序。

上述代码定义了一个简单的Spark SQL应用。具体步骤为:定义SparkSession;定义DataSet或DataFrame,应用程序可以从数据文件、现有RDD、Hive表或Spark数据源创建,上述代码通过JSON文件创建一个DataSet;使用DataSet或DataFrame,应用程序可以基于DataFrame做表结构的展示或者查询操作。JSON文件的数据具体如下。

(3)打包和作业提交。

在项目的根目录下输入如下命令对项目进行打包,打包后的程序在Target目录下。

Spark SQL的作业提交与一般的作业提交类似,具体代码如下。

在作业提交后,通过日志可以看到打印出以下Spark SQLDataSet中的数据和Schema信息。

(4)其他简单的操作。

  • ①查询name列上的数据,具体代码和输出结果如下。
  • ②基于age列做groupBy分组,然后执行count统计操作,具体代码和输出结果如下。

    除了简单的字段引用和表达式支持,DataFrame还提供了丰富的工具函数库,包括字符串组装、日期处理、常见的数学函数等。
2.4.6、Spark SQL的视图操作

SparkSession上的SQL函数可以通过编程的方式运行SQL语句,并将结果作为DataSet返回。

应用程序可以使用如下方式将DataFrame转化为一个视图,并基于视图进行查询操作。从使用层面上,SparkSQL的视图可以理解为与数据库视图类似,只不过数据库视图是对数据库表的抽象,而Spark SQL的视图是对DataFrame的抽象。Spark SQL的视图可分为临时视图和全局临时视图。

  • (1)临时视图:Spark SQL的临时视图是会话范围的, 如果创建它的SparkSession终止,则它将消失。创建临时视图的代码如下。
  • (2)全局临时视图:如果希望拥有一个在所有会话之间共享的临时视图并保持活动状态,直到Spark应用程序终止,则可以通过创建全局临时视图的方式来实现。全局临时视图与系统保留的数据库global_temp绑定,应用程序必须使用限定名称来引用它。具体使用如下。

2.4.7、创建DataSet

Saprk SQL DataSet API提供的API与RDD类似,但是DataSet并没有使用Kryo实现序列化,而是使用Spark提供的Encoder(编码器)序列化对象,以便通过网络进行处理或传输。虽然Encoder和标准序列化都负责将对象转换为字节,但Encoder是动态生成代码的,并允许Spark执行更多操作(例如过滤、排序和散列)​,而不需要将字节数组反序列化为对象。通过Encoder创建数据集的具体步骤如下。

  • (1)创建Java Bean:创建基于Java Bean的数据结构,Spark会将Java Bean中的属性映射为视图的字段,也可以理解为表的字段。下面定义一个简单的Persion类,用于表示Java Bean。
  • (2)创建Encoder:基于Java Bean创建对应的Encoder。
  • (3)创建DataSet并使用:根据Encoder和Java Bean创建DataSet,这个时候应用程序就可以基于DataSet做交互式查询操作。创建Encoder和DataSet的代码如下。

除了基于Java Bean创建具有明确属性的数据结构,应用程序还可以使用Encoder创建基本数据类型的结构,具体实现如下。

除了基于Java Bean创建DataSet,应用程序还可以从JSON文件创建DataSet,具体实现如下。

2.4.8、DataSet与RDD相互转换
  • (1)利用反射来推断Schema:Spark SQL支持两种不同方法将现有的RDD转换为DataSet。第一种方法利用反射机制来推断包含特定类型对象的RDD的Schema。这种基于反射的方法可以提供更简洁的代码,这种模式要求编写Spark应用程序是在已经了解数据模型的前提下。具体实现代码如下。

  • (2)以编程方式指定Schema:如果无法提前定义JavaBean的数据类型,则可以通过编程方式创建Dataset<Row>,其创建过程分为以下3步。
    • ①从已有的RDD创建一个包含Row对象的RDD。如下代码创建一个名为peopleRDD1的RDD。

      其中textpath内的数据如下。
    • ②通过StructType创建一个Schema,与步骤①中创建的RDD的结构相匹配。在如下代码中,根据fields字段列表定义的Schema的结构与根据textpath构建的peopleRDD1相对应,都包含name和age两个属性。
    • ③通过SparkSession提供的createDataFrame方法将Schema应用于rowRDD,然后应用程序就可以基于该DataFrame执行SQL查询。具体实现代码如下。

  • (3)DataFrames聚合操作:除了基本的转换操作,DataFrame函数还提供了一些聚合操作,例如,count()、countDistinct()、avg()、max()、min()等。此外,除了使用Spark预定义的聚合函数,应用程序可以创建自己的聚合函数。
2.4.9、DataFrame数据的加载

DataFrame支持以文本文件、JSON、Parquet、ORC等多种数据源的方式加载和保存数据。

  • (1)读取和写入Parquet文件:下面代码从Parquet文件加载数据,查询出name和age对应的数据后再保存到新的Parquet文件中。
  • (2)手动指定文件类型:除了直接调用read()加载数据源,应用程序还可以手动指定要加载的数据源。常用的数据源类型有JSON、Parquet、JDBC、ORC、LIBSVM、CSV、Text。如下代码加载JSON文件,查询出name和age对应的数据后再保存到Parquet文件中。
  • (3)直接在文件上运行SQL:应用程序可以直接使用SQL查询该文件,而不是通过读取API将文件加载到DataFrame再进行查询。如下代码直接基于Parquet文件执行SQL查询语句。
2.4.10、DataFrame数据的保存

在基于DataFrame完成计算后可以将计算结果保存到内存或外部存储系统。

  • (1)数据保存模式:DataFrame保存操作可以使用SaveMode设置保存数据的模式,具体有如下保存模式。
    • SaveMode.ErrorIfExists(default):默认模式,当从DataFrame向数据源保存数据时,如果数据已经存在,则抛出异常。
    • SaveMode.Append:如果数据或表已经存在,则将DataFrame的数据追加到已有数据的尾部。
    • SaveMode.Overwrite:如果数据或表已经存在,则使用DataFrame数据覆盖之前的数据。
    • SaveMode.Ignore:如果数据已经存在,则放弃保存DataFrame数据。这与SQL里的CREATE TABLE IF NOTEXISTS类似。
  • (2)保存到持久化表:在使用HiveContext的时候,DataFrame可以用saveAsTable方法将数据保存成持久化表。与registerTempTable不同,saveAsTable会将DataFrame的实际数据保存下来,并且在HiveMetastore中创建一个游标指针。持久化表会一直保留,即使Spark程序重启也不受影响,只要程序连接到同一个Metastore就可以读取其数据。当读取持久化表时,只需要把表名作为参数,调用SQLContext.table方法即可得到对应的DataFrame。

2.5、Spark Structured Streaming的使用

2.5.1、Spark Structured Streaming简介

Spark Structured Streaming(Spark结构化流)是基于Spark SQL引擎扩展的流处理引擎,使得用户可以基于SQL像处理静态数据一样处理流式计算。Spark SQL引擎将负责不断地运行Structured Streaming,并在流数据持续到达时更新最终结果。同时,Spark StructuredStreaming通过检查点和预写日志确保端到端的一次性容错保证。简而言之,Spark Structured Streaming提供快速、可扩展、容错、端到端的精确一次流处理,而不需要用户关心具体的复杂流处理实现。

在默认情况下,Spark Structured Streaming使用微批处理引擎进行处理,该引擎将数据流作为一系列小批量作业处理,从而实现低至100ms的端到端延迟和完全一次的容错保证。

从Spark 2.3后引入了一种被称为Continuous Processing的新的低延迟处理模式,可以实现低至1ms的端到端延迟,并且提供至少一次的容错保证。

2.5.2、Spark Structured Streaming的数据模型

Spark Structured Streaming的核心思想是将连续不断的数据流看作一个不断被连续追加的无界数据表,这样源源不断的流式数据计算将被抽象为基于增量数据表的SQL操作。用户只需要定义SQL操作的方法,Spark StructuredStreaming会在有增量数据时将增量数据收集起来组成微批数据,并在该数据上执行SQL操作,以完成基于SQL的流式计算。Spark Structured Streaming的数据模型如图所示。

Spark Structured Streaming的数据流被看成表的行数据,连续地向表中追加。Spark Structured Streaming查询将会产生一个结果表(Result Table)​,第一行是时间线,每秒有一个触发器,第二行是输入流,对输入流执行查询后产生的结果最终会被更新到第三行的结果表中。第四行是查询结果输出。Spark Structured Streaming的查询流程如图所示。

Spark Structured Streaming的查询结果输出有3种不同的模式。

  • 完全模式(Complete Mode):将计算结果更新整个结果表。
  • 追加模式(Append Mode):将上一次触发到当前时间段内的数据追加到结果表的新行,并写入外部存储。
  • 更新模式(Update Mode):将上一次触发到当前时间段内的数据在结果表中更新的行写入外部存储。这种模式不同于完全模式,它仅仅输出上一次触发后改变的行。
2.5.3、创建一个Spark Structured Streaming应用

在上述Spark项目的基础上创建Spark StructuredStreaming项目,具体步骤如下。

(1)新建SSSimple类。

在Spark项目的Java目录下新建一个名为SSSimple(Structured Streaming Simple)的Java类,并在类中输入下面代码构建一个简单的Spark StructuredStreaming程序。该程序监听TCP端口上的数据并进行实时处理。

上述代码定义了一个简单的Spark Structured Streaming应用。具体步骤为:定义SparkSession;定义DataFrame,注意这里使用spark.readStream(​)监听TCP端口的数据并实时转换为DataSet;定义数据操作,这里基于DataFrame做聚合操作;启动StreamingQuery实时流计算。

在上述代码中,名为lines的DataFrame表示一个包含流文本数据的无界表,该表包含一列名为value的字符串,并且流式数据中的每一条数据都将映射为表中的一行数据。然后,使用lines.as(Encoders.STRING(​)​)将DataFrame转换为名为words的Dataset,以便应用程序可以应用flatMap操作将每行都拆分为多个单词。words中包含了所有单词。接着,通过words.groupBy("value").count(​)对Dataset中的Value值进行分组统计操作,并将计算结果定义成名为wordCounts的DataFrame。注意wordCounts是一个流式DataFrame,它实现流式数据的实时查询。最后,通过调用start(​)方法启动该流式计算。

(2)启动TCP服务端。

在Linux上通过Netcat启动一个TCP服务端,具体代码如下。

(3)打包和作业提交。

在项目的根目录下输入如下命令对项目进行打包,打包后的程序在Target目录下。

SparkStreaming的作业提交与一般的作业提交类似,具体代码如下。

在Netcat窗口中输入"hello java"​,查看Spark日志,看到输出以下统计结果。

通过上述日志,我们不但能看到具体的执行结果,还能明确地看到Spark内部Streaming Query的执行过程。该执行过程不但记录了runId和batchId等流式计算的描述信息,同时,定义了stateOperator列表,用于状态监控,定义了source,表示实时查询的数据源,定义了sink,表示最终数据处理结果的输出。

相关推荐
吴声子夜歌1 小时前
Java面试——Spark原理及应用(一)
java·面试·spark
慧一居士2 小时前
ThreadLocal 功能及使用场景介绍,使用详解
java
zh73142 小时前
typephp的编译核心phpx原理解析
android·java·开发语言
2401_894915534 小时前
GEO 优化源码全解析:从搜索引擎到 AI 引擎的底层改写逻辑
java·服务器·前端·数据库·人工智能·分布式·搜索引擎
Java牛马8 小时前
SpringBoot Starter 依赖相关总结
java·spring·springboot·starter·自动装配·依赖
吴声子夜歌8 小时前
Java面试——Spring Cloud原理及应用(二)
java·spring cloud·面试
东小西9 小时前
【SAA实战】第 1 篇:ReactAgent 入门——先撸个"会调工具的助手"跑起来
java·人工智能·spring
黄敬峰9 小时前
一文搞懂 Docker + Dockerfile:从镜像构建到全栈部署
面试
mqiqe10 小时前
AgentScope Java 2.0 协议集成全景解析:A2A、AG-UI、Agent Protocol 三大开放协议实战指南
java·开发语言·ui