Structured Streaming整合kafka

Structured Streaming整合kafka

Spark2.0以后开始推出Structured Streaming,详情参考上文Spark2.0 Structured Streaming。本文介绍一种常用的方式: Structured Streaming读取kafka数据,并使用spark sql过滤,最终输出到终端。

本示例能够读取多个topic数据,并分别映射为Spark内存表,执行多个sql。

1.环境

(1)版本

Spark版本:spark-2.2.0

Scala版本:scala-2.11.4

Kafka版本:kafka 0.10 or higher

(2)数据

Kafka topic:dyl_test01

数据内容:{"a":"1","b":"2","c":"2018-01-08","d":[23.9,45]}

2.基本介绍

2.1 Linking

使用maven/SBT的项目,需要引用如下的库文件:

groupId = org.apache.spark                                                                                                                                                                                

artifactId = spark-sql-kafka-0-10_2.11

version = 2.2.1

其他jar

<dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-streaming_2.11</artifactId>
      <version>${spark.version}</version>
      <scope>${scope}</scope>

    </dependency>

<dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
      <version>${spark.version}</version>
    </dependency>

<dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.11</artifactId>
      <version>${spark.version}</version>
      <scope>${scope}</scope>

    </dependency>

<dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-core_2.11</artifactId>
      <version>${spark.version}</version>
      <scope>${scope}</scope>

    </dependency>





2.2 Reading Data from Kafka

(1)直接从Kafka读取数据,并且进行查询:

// 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)]

 

这个没什么说的,简单的设置kafka集群参数以及topic,然后进行查询,df.selectExpr中能够使用sql里的语法,df.select里只能设置选择的字段。设置多个kafka topic时,可以逗号分割,或者正则匹配,这时候,所有topic的数据都会写入到一张表中,很多时候,不同topic更想映射到不同的表中,稍后说明。先来看看直接读取Kafka数据并输出时,结果如何,如下图2.1所示:

图2.1 直接读取kafka数据输出

如上图2.1,是没有执行selectExpr("CAST(value AS STRING)"的结果,value是字节数组,所以,执行CAST的作用是将value转换为字符串,结果如下2.2所示:

图2.2 value转为字符串

此时,selectExpr中只选择了key和value,所以结果中也只有key和value字段。读取kafka时,默认并不会解析kafka的数据内容,而是直接将kafka数据放到value列中显示出来,关于其他几个字段如下表2.1所示:

表2.1 默认内存表schema

Column                                                                                                                   

Type                                                                                                                      

key

binary

value

binary

topic

string

partition

int

offset

long

timestamp

long

timestampType

int

关于Kafka source的详细配置,可以参考官网:Kafka Integration Guide (Kafka broker version 0.10.0 or higher)。这里不多介绍,kafka的数据被写入value,其实对用户而言,并没有什么用,除非使用正则去匹配value的内容,我们更想解析value的内容,将value的内容映射为一张表,如上,value内容是JSON格式数据,解析方式也很简单,使用from_json函数即可,这里有个例子可以参考:kafka-spark-structured-streaming-example,主要内容如下所示:

valschema=StructType(Seq(

StructField("schema",StringType, true),

StructField("payload",StringType, true)

))

 
 

valdf= ds1.selectExpr("cast (value as string) as json")

.select(from_json($"json",schema=schema).as("data"))

.select("data.payload")

首先,将value字段转换为字符串,重名了为"json",然后使用from_json函数,应用预定义的schema,解析json数据,将结果重命名为"data",接下来就可以直接使用json中的字段了。

这里有一个比较纠结的地方,必须预定义json的格式,而不是像spark Streaming一样,提供了自动推测json格式的功能,让我一度觉得Structured Streaming太局限,不实用,经过查证以及思考,觉悟这原来是一个优化,json格式自动推测的前提是遍历所有数据,收集信息才能正确推测json的字段和字段类型,这无疑是很慢的,也很消耗资源,实际上,json格式字段事先基本上都是能够知道的,发生更改的情况很少,但是,还是有可能发生的,目前,我没找到如何动态更改json schema的方式,提供了Schema之后,spark生成的任务计划中已经写死了json数据解析的字段名和字段类型,当查询任务start之后,则使用该任务计划解析数据,并没有提供修改scheme的接口,但是通过查看源码,发现有个RuntimeReplaceable Expression,例子是spark的nvl函数,好像可以运行时更改选择列的表达式,没细看,知道的朋友交流一下,也因此,实际中并没有使用Structured Steamming,因为项目中需要修改json的解析方式。

经过from_json解析之后,结果如下图2.3所示:

图2.3 from_json解析数据

完整代码如下:

  1. package com.dyl.pwrd  
  2.    
  3. import org.apache.spark.SparkConf  
  4. import org.apache.spark.sql.functions._  
  5. import org.apache.spark.sql.streaming.StreamingQuery  
  6. import org.apache.spark.sql.types._  
  7. import org.apache.spark.sql.SparkSession  
  8.    
  9. import scala.collection.mutable  
  10. /** 
  11.   * Created by dongyunlong on 2018/1/5. 
  12.   * {"a":"1","b":"2","c":"2018-01-08","d":[23.9,45]} 
  13.   */  
  14. object SparkStructuredStreaming {  
  15.    
  16.   /** 
  17.     * 创建SparkSession 
  18.     * @return 
  19.     */  
  20.   def getSparkSession={  
  21.     SparkSession  
  22.       .builder()  
  23.       .config(new SparkConf().setMaster("local[2]"))  
  24.       .appName(getClass.getName)  
  25.       .getOrCreate()  
  26.   }  
  27.    
  28.   /** 
  29.     * 解析kafka json数据,并将其映射为spark临时表 
  30.     * @param spark 
  31.     * @param kafkaTopic 
  32.     * @param sourceName 
  33.     */  
  34.   def createOrReplaceTempView(spark:SparkSession, kafkaTopic:String, sourceName:String): Unit ={  
  35.     import spark.implicits._  
  36.     val df = spark  
  37.       .readStream  
  38.       .format("kafka")  
  39.       .option("kafka.bootstrap.servers""XX.XX.XX.XX:9092")  
  40.       .option("subscribe", kafkaTopic)  
  41.       .option("startingOffsets""earliest")  
  42.     .load()  
  43. //    val schema = SocSchemaCollection.getSchemaBySourceName(sourceName) //从数据库加载json schema  
  44.     val schema = StructType(mutable.Seq(  
  45.       StructField("a", DataTypes.StringType),  
  46.       StructField("b", DataTypes.StringType),  
  47.       StructField("c", DataTypes.StringType),  
  48.       StructField("d", DataTypes.createArrayType(DataTypes.StringType))  
  49.     ))  
  50.     if(schema != null){  
  51.       val jsonDf = df.selectExpr("CAST(key AS STRING)""cast (value as string) as json")  
  52.           .select(from_json($"json", schema=schema).as("data"))  
  53.       jsonDf.select("data.*").createOrReplaceTempView(sourceName)  
  54.     }else{  
  55.       println("error,schema is null")  
  56.     }  
  57.   }  
  58.    
  59.   /** 
  60.     * 输出spark sql的查询结果 
  61.     * @param spark 
  62.     * @param sql 
  63.     * @return 
  64.     */  
  65.   def sqlWriteStream(spark:SparkSession, sql:String): StreamingQuery ={  
  66.     val query = spark.sql(sql)  
  67.       .writeStream  
  68.       .outputMode("append")  
  69.       .format("console")  
  70.       .start()  
  71.     query  
  72.   }  
  73.    
  74.   /** 
  75.     * 注册spark临时表,执行sql语句,注意这里每一个sql都是一个writeStream,最后使用spark.streams.awaitAnyTermination()等待所有查询 
  76.     * @param spark 
  77.     */  
  78.   def sparkReadKafka(spark:SparkSession): Unit ={  
  79.     createOrReplaceTempView(spark, "dyl_test01""dyl_test")  
  80.     val sqls = Array("select * from dyl_test","select *,'2' as e from dyl_test")  
  81.     val querys = mutable.ListBuffer[StreamingQuery]()  
  82.     for(sql <- sqls){  
  83.       println(sql)  
  84.       querys += sqlWriteStream(spark, sql)  
  85.     }  
  86.   }  
  87.    
  88.   /** 
  89.     * 主函数 
  90.     * @param args 
  91.     */  
  92.   def main(args: Array[String]) {  
  93.     println("hello world")  
  94.     val spark = getSparkSession  
  95.     sparkReadKafka(spark)  
  96.     spark.streams.awaitAnyTermination()  
  97.   }  
  98. }  
  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值