Spark Structured Streaming框架(2)之数据输入源详解
Spark Structured Streaming目前的2.1.0版本只支持输入源:File、kafka和socket。
1. Socket
Socket方式是最简单的数据输入源,如Quick example所示的程序,就是使用的这种方式。用户只需要指定"socket"形式并配置监听的IP和Port即可。
| 
 val scoketDF = spark.readStream .format("socket") .option("host","localhost") .option("port", 9999) .load()  | 
注意:
Socket方式Streaming是接收UTF8的text数据,并且这种方式最后只用于测试,不要用户端到端的项目中。
2. Kafka
Structured streaming提供接收kafka数据源的接口,用户使用起来也非常方便,只是需要注意开发环境所依赖的特别库,同时streaming运行环境的kafka版本。
2.1 开发环境
若以kafka作为输入源,那么开发环境需要再引入所依赖的架包。如使用了Spark版本是2.1.0,那么maven的pom.xml文件中需要添加如下的依赖库。
| 
 <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql-kafka-0.10_2.11</artifactId> <version>2.1.0</version> </dependency>  | 
2.2 API
与使用socket作为输入源类似,只需要指定"kafka"作为输入源,同时传递kafka的server集和topic集。如下所示:
| 
 // Subscribe to 1 topic val df = spark .readStream .format("kafka") .option("kafka.bootstrap.servers", "host1:port1,host2:port2") .option("subscribe", "topic1") .load() df.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)") .as[(String, String)] // Subscribe to multiple topics val df = spark .readStream .format("kafka") .option("kafka.bootstrap.servers", "host1:port1,host2:port2") .option("subscribe", "topic1,topic2") .load() df.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)") .as[(String, String)] // Subscribe to a pattern val df = spark .readStream .format("kafka") .option("kafka.bootstrap.servers", "host1:port1,host2:port2") .option("subscribePattern", "topic.*") .load() df.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)") .as[(String, String)]  | 
2.3 运行环境
由于spark 2.1.0使用了kafka的版本是0.10,所以kafka server也要使用同样版本,即发送数据的kafka也需要使用0.10版本。
否则会出现如下的错误:

图 21
3. File
Structured Streaming可以指定一个目录的文件作为数据输入源,其中支持的文件格式有:text、csv、json、parquet。
如下所示:
| 
 object StructuredFile{ def main(args:Array[String]){ val spark = SparkSession .builder .appName("StructuredNetWordCount") .getOrCreate() val userSchema = new StructType().add("name","string").add("age","integer") val jsonDF = spark .readStream .schema(userSchema) .json("/root/jar/directory")//Equivalent to format("json").load("/root/jar/directore") Val query = jsonDF.writeStream .format(console) .start() Query.awaitTermination() } }  | 
读取文件的接口有5个:
- format(source).load(path):source参数是指文件的形式,有text、csv、json、parquet四种形式;
 - text(path):其封装了format("text").load(path);
 - json(path):其封装了format("json").load(path);
 - csv(path):其封装了format("csv").load(path);
 - parquet(path):其封装了format("parquet").load(path);
 
其中path参数为文件的路径,若该路径发现新增文件,则会被以数据流的形式被获取。但该路径只能是指定的格式文件,不能存放其它文件格式。
注意:
若是以Spark集群方式运行,则路径是hdfs种的文件路径;若是以local方式执行,则路径为本地路径。
获取的文件形式有四种,但并不是每种格式都需要调用schema()方法来配置文件信息:
- csv、json、parquet:用户需要通过schema()方法手动配置文件信息;
 - text:不需要用户指定schema,其返回的列是只有一个"value"。
 
4) 自定义
若上述Spark Structured Streaming API提供的数据输入源不能满足要求,那么还有一种方法可以使用:修改源码。
如下通过获取"socket"数据源相应类的内容为例,介绍具体使用方式:
4.1 实现Provider
首先实现一个Provider,该类会返回一个数据的数据源对象。其中Provider实现类需要实现三个方法:
| 
 序号  | 
 方法  | 
 描述  | 
| 
 1  | 
 souceSchema  | 
 该方法返回一个配置信息的词典,key是字符串,value是StructType对象  | 
| 
 2  | 
 createSource  | 
 该方法返回一个接受数据源的对象,其为Source接口的子类  | 
| 
 3  | 
 shortName  | 
 该方法返回一个数据源的标识符,如上述format()方法传递的参数:"socket"、"json"或"kafka";此时返回的字符串,就是format()方法传递的参数  | 
如下所示实现一个TextRabbitMQSourceProvider类:
| 
 class TextRabbitMQSourceProvider extends StreamSourceProvider with DataSourceRegister with Logging { private def parseIncludeTimestamp(params: Map[String, String]): Boolean = { Try(params.getOrElse("includeTimestamp", "false").toBoolean) match { case Success(bool) => bool case Failure(_) => throw new AnalysisException("includeTimestamp must be set to either \"true\" or \"false\"") } } /** Returns the name and schema of the source that can be used to continually read data. */ override def sourceSchema( sqlContext: SQLContext, schema: Option[StructType], providerName: String, parameters: Map[String, String]): (String, StructType) = { logWarning("The socket source should not be used for production applications! " + "It does not support recovery.") if (!parameters.contains("host")) { throw new AnalysisException("Set a host to read from with option(\"host\", ...).") } if (!parameters.contains("port")) { throw new AnalysisException("Set a port to read from with option(\"port\", ...).") } val schema = if (parseIncludeTimestamp(parameters)) { TextSocketSource.SCHEMA_TIMESTAMP } else { TextSocketSource.SCHEMA_REGULAR } ("textSocket", schema) } override def createSource( sqlContext: SQLContext, metadataPath: String, schema: Option[StructType], providerName: String, parameters: Map[String, String]): Source = { val host = parameters("host") val port = parameters("port").toInt new TextRabbitMQSource(host, port, parseIncludeTimestamp(parameters), sqlContext) } /** String that represents the format that this data source provider uses. */ override def shortName(): String = "RabbitMQ" }  | 
4.2 实现Source
用户需要实现一个真正接受数据的类,该类实例是由Provider实现类来实例化,如上述的createSource()方法。其中需要实现Source抽象类的几个方法,从而让Structured Streaming引擎能够调用:
| 
 序号  | 
 方法  | 
 描述  | 
| 
 1  | 
 getOffset  | 
 获取可用的数据偏移量,表明是否有可用的数据  | 
| 
 2  | 
 getBatch  | 
 获取可用的数据,以DataFrame对象形式返回  | 
| 
 3  | 
 commit  | 
 传递已经接收的数据偏移量  | 
| 
 4  | 
 stop  | 
 听着Source数据源  | 
| 
 class TextRabbitMQSource(host: String, port: Int, includeTimestamp: Boolean, sqlContext: SQLContext) extends Source with Logging { @GuardedBy("this") private var socket: Socket = null @GuardedBy("this") private var readThread: Thread = null /** * All batches from `lastCommittedOffset + 1` to `currentOffset`, inclusive. * Stored in a ListBuffer to facilitate removing committed batches. */ @GuardedBy("this") protected val batches = new ListBuffer[(String, Timestamp)] @GuardedBy("this") protected var currentOffset: LongOffset = new LongOffset(-1) @GuardedBy("this") protected var lastOffsetCommitted : LongOffset = new LongOffset(-1) initialize() private def initialize(): Unit = synchronized { socket = new Socket(host, port) val reader = new BufferedReader(new InputStreamReader(socket.getInputStream)) readThread = new Thread(s"TextSocketSource($host, $port)") { setDaemon(true) override def run(): Unit = { try { while (true) { val line = reader.readLine() if (line == null) { // End of file reached logWarning(s"Stream closed by $host:$port") return } TextSocketSource.this.synchronized { val newData = (line, Timestamp.valueOf( TextSocketSource.DATE_FORMAT.format(Calendar.getInstance().getTime())) ) currentOffset = currentOffset + 1 batches.append(newData) } } } catch { case e: IOException => } } } readThread.start() } /** Returns the schema of the data from this source */ override def schema: StructType = if (includeTimestamp) TextSocketSource.SCHEMA_TIMESTAMP else TextSocketSource.SCHEMA_REGULAR override def getOffset: Option[Offset] = synchronized { if (currentOffset.offset == -1) { None } else { Some(currentOffset) } } /** Returns the data that is between the offsets (`start`, `end`]. */ override def getBatch(start: Option[Offset], end: Offset): DataFrame = synchronized { val startOrdinal = start.flatMap(LongOffset.convert).getOrElse(LongOffset(-1)).offset.toInt + 1 val endOrdinal = LongOffset.convert(end).getOrElse(LongOffset(-1)).offset.toInt + 1 // Internal buffer only holds the batches after lastOffsetCommitted val rawList = synchronized { val sliceStart = startOrdinal - lastOffsetCommitted.offset.toInt - 1 val sliceEnd = endOrdinal - lastOffsetCommitted.offset.toInt - 1 batches.slice(sliceStart, sliceEnd) } import sqlContext.implicits._ val rawBatch = sqlContext.createDataset(rawList) // Underlying MemoryStream has schema (String, Timestamp); strip out the timestamp // if requested. if (includeTimestamp) { rawBatch.toDF("value", "timestamp") } else { // Strip out timestamp rawBatch.select("_1").toDF("value") } } override def commit(end: Offset): Unit = synchronized { val newOffset = LongOffset.convert(end).getOrElse( sys.error(s"TextSocketStream.commit() received an offset ($end) that did not " + s"originate with an instance of this class") ) val offsetDiff = (newOffset.offset - lastOffsetCommitted.offset).toInt if (offsetDiff < 0) { sys.error(s"Offsets committed out of order: $lastOffsetCommitted followed by $end") } batches.trimStart(offsetDiff) lastOffsetCommitted = newOffset } /** Stop this source. */ override def stop(): Unit = synchronized { if (socket != null) { try { // Unfortunately, BufferedReader.readLine() cannot be interrupted, so the only way to // stop the readThread is to close the socket. socket.close() } catch { case e: IOException => } socket = null } } override def toString: String = s"TextSocketSource[host: $host, port: $port]" }  | 
4.3 注册Provider
由于Structured Streaming引擎会根据用户在format()方法传递的数据源类型来寻找具体数据源的provider,即在DataSource.lookupDataSource()方法中寻找。所以用户需要将上述实现的Provider类注册到Structured Streaming引擎中。所以用户需要将provider实现类的完整名称添加到引擎中的某个,这个地方就是在Spark SQL工程中的\spark-2.2.0\sql\core\src\main\resources\META-INF\services\org.apache.spark.sql.sources.DataSourceRegister文件中。用户通过将Provider实现类名称添加到该文件中,从而完成Provider类的注册工作。
如下所示在文件最后一行添加,我们自己自定义的实现类完整路径和名称:
| 
 org.apache.spark.sql.execution.datasources.csv.CSVFileFormat org.apache.spark.sql.execution.datasources.jdbc.JdbcRelationProvider org.apache.spark.sql.execution.datasources.json.JsonFileFormat org.apache.spark.sql.execution.datasources.parquet.ParquetFileFormat org.apache.spark.sql.execution.datasources.text.TextFileFormat org.apache.spark.sql.execution.streaming.ConsoleSinkProvider org.apache.spark.sql.execution.streaming.TextSocketSourceProvider org.apache.spark.sql.execution.streaming.RateSourceProvider org.apache.spark.sql.execution.streaming.TextRabbitMQSourceProvider  | 
4.4 使用API
再Spark SQL源码重新编译后,并肩其jar包丢进Spark的jars路径下。从而用户就能够像使用Structured Streaming自带的数据输入源一样,使用用户自定义的"RabbitMQ"数据输入源了。即用户只需将RabbitMQ字符串传递给format()方法,其使用方式和"socket"方式一样,因为上述的数据源内容其实是Socket方式的实现内容。
5. 参考文献
[1]. Structured Streaming Programming Guide.
Spark Structured Streaming框架(2)之数据输入源详解的更多相关文章
- Spark Structured Streaming框架(3)之数据输出源详解
		
Spark Structured streaming API支持的输出源有:Console.Memory.File和Foreach.其中Console在前两篇博文中已有详述,而Memory使用非常简单 ...
 - Spark Structured Streaming框架(2)之数据输入源详解
		
Spark Structured Streaming目前的2.1.0版本只支持输入源:File.kafka和socket. 1. Socket Socket方式是最简单的数据输入源,如Quick ex ...
 - Spark Structured streaming框架(1)之基本使用
		
Spark Struntured Streaming是Spark 2.1.0版本后新增加的流计算引擎,本博将通过几篇博文详细介绍这个框架.这篇是介绍Spark Structured Streamin ...
 - Spark Structured Streaming框架(1)之基本用法
		
Spark Struntured Streaming是Spark 2.1.0版本后新增加的流计算引擎,本博将通过几篇博文详细介绍这个框架.这篇是介绍Spark Structured Streamin ...
 - Spark Structured Streaming框架(4)之窗口管理详解
		
1. 结构 1.1 概述 Structured Streaming组件滑动窗口功能由三个参数决定其功能:窗口时间.滑动步长和触发时间. 窗口时间:是指确定数据操作的长度: 滑动步长:是指窗口每次向前移 ...
 - Spark Structured Streaming框架(5)之进程管理
		
Structured Streaming提供一些API来管理Streaming对象.用户可以通过这些API来手动管理已经启动的Streaming,保证在系统中的Streaming有序执行. 1. St ...
 - Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十九):推送avro格式数据到topic,并使用spark structured streaming接收topic解析avro数据
		
推送avro格式数据到topic 源代码:https://github.com/Neuw84/structured-streaming-avro-demo/blob/master/src/main/j ...
 - Kafka:ZK+Kafka+Spark Streaming集群环境搭建(十一)定制一个arvo格式文件发送到kafka的topic,通过Structured Streaming读取kafka的数据
		
将arvo格式数据发送到kafka的topic 第一步:定制avro schema: { "type": "record", "name": ...
 - Spark2.3(三十五)Spark Structured Streaming源代码剖析(从CSDN和Github中看到别人分析的源代码的文章值得收藏)
		
从CSDN中读取到关于spark structured streaming源代码分析不错的几篇文章 spark源码分析--事件总线LiveListenerBus spark事件总线的核心是LiveLi ...
 
随机推荐
- vi/vim复制粘贴命
			
1. 选定文本块.使用v进入可视模式,移动光标键选定内容. 2.复制的命令是y,即yank(提起) ,常用的命令如下: y 在使用v模式选定了某一块的时候,复制选定块到缓冲区用: ...
 - 基于 Token 的身份验证
			
最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强, ...
 - 安卓SAX解析XML文件
			
XML文件经常使用的解析方式有DOM解析,SAX解析. 一.Sax SAX(simpleAPIforXML)是一种XML解析的替代方法. 相比于DOM.SAX是一种速度更快,更有效的方法. 它逐行扫描 ...
 - 《TomCat与Java Web开发技术详解》(第二版) 第四章节的学习总结--常用Servlet API
			
要开发Servlet,自然要掌握常用的servlet的相关API.通过此章节的学习,了解到如下常用API 1.Servlet接口--->GenericServlet抽象类(实现Servlet接口 ...
 - nginx大量TIME_WAIT的解决办法   netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
			
vi /etc/sysctl.conf net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse=1 #让TIME_WAIT状态可以重用,这样即使TIME_W ...
 - 【Mac + Python】苹果系统之安装Python3.6.x环境
			
一.打开终端 输入:uname -a ,查看电脑系统位数. 输入:python,查看mac系统python版本. 二.为了以后切换版本方便,安装pyenv进行版本切换以及升级. 参考文章:<M ...
 - httpClient实现
			
1.实现功能 向关注了微信公众号的微信用户群发消息.(可以是所有的用户,也可以是提供了微信openid的微信用户集合) 2.基本步骤 前提: 已经有认证的公众号或者测试公众账号 发送消息步骤: 发送一 ...
 - COM组件多接口对象模型
			
COM组件有两种接口类型,Dual and Custom,如下图所示.本文说的是Custom.所谓多接口COM对象是指此COM对象实现了多于一个的自定义接口,即Custom接口. 接口图如下: 需要注 ...
 - thinkPHP5.0的学习研究【架构】
			
2017年6月19日18:51:53 架构:1.ThinkPHP5.0应用基于MVC(模型-视图-控制器)的方式来组织.2.MVC是一个设计模式,它强制性的使应用程序的输入.处理和输出分开.使用MVC ...
 - ASIHTTP 框架,同步、  异步请求、  上传 、 下载
			
ASIHTTPRequest详解 ASIHTTPRequest 是一款极其强劲的 HTTP 访问开源项目.让简单的 API 完成复杂的功能,如:异步请求,队列请求,GZIP 压缩,缓存,断点续传,进度 ...