SparkStreaming 的用途(流式计算的应用场景)
1. 实时统计,累加(淘宝的大屏)
kafka + sparkstreaming(updateStatByKey,mapwithState)
2. 实时统计,最近一段时间指标
实时查看最近一个小时之内的用户点击量,各省或者重点城市(window窗口)
工作原理:
Spark Core
RDD
sc =new SparkContext(conf) :获取RDD
Spark SQL
DataFrame/DataSet
sqlContext=new SQLContext(sc) /HiveContext :获取DataFrame
Spark Streaming
DStream
ssc=new StreamingContext(sc,Seconds(1)); :
SparkStreaming的运行的原理
DStream分割的原理
Block interval:默认是200ms ,这个值是可以通过配置区修改的。
Batch inverval:这个值就是我们每次处理多长时间范围内的数据。是在new StreamingContext的时候就指定。
pom.xml的依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.10</artifactId>
<version>1.6.2</version>
</dependency>
安装nc工具。
NetCat是一个非常简单的Unix工具,可以读、写TCP或UDP网络连接(network connection)。它被设计成一个可靠的后端(back-end)工具,能被其它的程序程序或脚本直接地或容易地驱动。
Yum install -y nc
Spark Streaming HDFS WordCount例子演示
import org.apache.spark.SparkConf
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.Seconds
object HDFSWordcCount {
def main(args: Array[String]): Unit = {
/**
* local[1] 中括号里面的数字都代表的是启动几个工作线程
* 默认情况下是一个工作线程。那么做为sparkstreaming 我们至少要开启
* 两个线程,因为其中一个线程用来接收数据,这样另外一个线程用来处理数据。
*/
val conf = new SparkConf().setMaster("local[2]").setAppName("HDFSWordcCount")
/**
* Seconds 指的是每次数据数据的时间范围 (bacth interval)
*/
val ssc = new StreamingContext(conf, Seconds(2));
val fileDS = ssc.textFileStream("hdfs://hadoop1:9000/hdfs")
val wordcountds = fileDS.flatMap { line => line.split("\t") }
.map { word => (word, 1) }
.reduceByKey(_ + _)
wordcountds.print()
ssc.start()
ssc.awaitTermination();
}
}
提交到spark集群进行运算的networdcount.sh文件
/usr/local/soft/spark/bin/spark-submit \
--class com.xxxx.streaming.NetWordCount \
--master spark://192.169.32.90:7077 \
--driver-memory 500m \
--executor-memory 500m \
--total-executor-cores 2 \
/usr/local/soft/streaming/networdcount.jar
修改:当新的数据添加后,在原始基础上继续计算
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
object NetWordCountUpdateStateByKey {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("NetWordCountUpdateStateByKey")
val ssc = new StreamingContext(conf, Seconds(2));
val fileDS = ssc.socketTextStream("hadoop1", 9999)
/**
* 需要设置一个checkpoint的目录
* 因为我们的计算结果有中间状态,这些中间状态需要存储
*/
ssc.checkpoint(".")
val wordDS = fileDS.flatMap { line => line.split("\t") }
.map { word => (word, 1) }
/**
* updateFunc: (Seq[Int], Option[S]) => Option[S]
* updateFunc 这是一个匿名函数
* (Seq[Int], Option[S]) 两个参数
* Option[S] 返回值
* 首先我们考虑一个问题
* wordDS 做的bykey的计算,说明里面的内容是tuple类型,是键值对的形式,说白了是不是
* 就是【K V】
* wordDS[K,V]
* (Seq[Int], Option[S])
* 参数一:Seq[Int] Seq代表的是一个集合,int代表的是V的数据类型
* ---分组的操作,key相同的为一组 (hadoop,{1,1,1,1})
* 参数二:Option[S] S代表的是中间状态State的数据类型,S对于我们的这个wordcount例子来讲,应该是
* int类型。中间状态存储的是单词出现的次数。 hadoop -> 4
*
* Option[S] 返回值 应该跟中间状态一样吧。
* Option Some/None
*
*/
val wordcountDS = wordDS.updateStateByKey((values: Seq[Int], state: Option[Int]) => {
val currentCount = values.sum; //获取此次本单词出现的次数
val count = state.getOrElse(0); //获取上一次的结果 也就是中间状态
Some(currentCount + count);
})
wordcountDS.print()
//启动应用
ssc.start()
//等待任务结束
ssc.awaitTermination()
}
}
mapwithstate
与updateStateByKey方法相比,使用mapWithState方法能够得到6倍的低延迟同时维护的key状态数量要多10倍.
此外,更快的处理速度使得mapWithState 方法能够比updateStateByKey 方法管理多10倍的key(批处理间隔、集群大小、更新频率全部相同)。
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] 代表的就是状态(历史状态,也就是上次的结果)
*
*/
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)))
val stateDstream = wordDstream.mapWithState(
StateSpec.function(mappingFunc).initialState(initialRDD))
stateDstream.print();
//启动应用
ssc.start()
//等待任务结束
ssc.awaitTermination()
}
}
transform算子
transform操作,应用在DStream上时,可以用于执行任意的RDD到RDD的转换操作。它可以用于实现,DStream API中所没有提供的操作。比如说,DStream API中,并没有提供将一个DStream中的每个batch,与一个特定的RDD进行join的操作。但是我们自己就可以使用transform操作来实现该功能。
代码目的: 单词计数,但是单词里面,不需要统计 ? , ! 。也就是说在单词计数的时候过滤黑名单里面的标点符号。
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
object TransformaDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("TransformaDemo")
val ssc = new StreamingContext(conf, Seconds(5));
val fileDS = ssc.socketTextStream("192.168.32.110", 9999);
val wordcountDS = fileDS.flatMap { line => line.split("\t") }
.map { word => (word, 1) }
val fillter = ssc.sparkContext.parallelize(List(",", "?", "!", ".")).map { param => (param, true) }
val needwordDS = wordcountDS.transform(rdd => {
val leftRDD = rdd.leftOuterJoin(fillter);
//leftRDD String,(int,option[boolean]);
val needword = leftRDD.filter(tuple => {
val x = tuple._1;
val y = tuple._2;
if (y._2.isEmpty) {
true;
} else {
false;
}
})
needword.map(tuple => (tuple._1, 1))
})
val wcDS = needwordDS.reduceByKey(_ + _);
wcDS.print();
ssc.start();
ssc.awaitTermination();
}
}
window操作
每隔4秒实时统计前6秒的单词计数情况.(每2秒生成一个RDD)
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
object reduceByKeyAndWindowDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("TransformaDemo")
val ssc = new StreamingContext(conf, Seconds(2));
val fileDS = ssc.socketTextStream("192.168.32.110", 9999);
val wordcountDS = fileDS.flatMap { line => line.split("\t") }
.map { word => (word, 1) } //(_+_)
.reduceByKeyAndWindow((x: Int, y: Int) => { x + y }, Seconds(6), Seconds(4))
wordcountDS.print();
ssc.start();
ssc.awaitTermination();
}
}
foreachRDD
通常在foreachRDD中,都会创建一个Connection,比如JDBC Connection,然后通过Connection将数据写入外部存储。
误区一:在RDD的foreach操作外部,创建Connection
这种方式是错误的,因为它会导致Connection对象被序列化后传输到每个Task中。而这种Connection对象,实际上一般是不支持序列化的,也就无法被传输。
误区二:在RDD的foreach操作内部,创建Connection
这种方式是可以的,但是效率低下。因为它会导致对于RDD中的每一条数据,都创建一个Connection对象。而通常来说,Connection的创建,是很消耗性能的。
使用方式一:
使用方式二:
使用数据库池实现实时单词计数:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
首先创建好连接池
import java.sql.DriverManager
import java.sql.Connection
object MysqlPool {
private val max = 8; //连接池的连接总数
private val connectionNum = 10; //每次产生的连接数
private var conNum = 0; //当前连接池已经产生的连接数
import java.util
private val pool = new util.LinkedList[Connection](); //连接池
{
Class.forName("com.mysql.jdbc.Driver")
}
/**
* 释放连接
*/
def releaseConn(conn: Connection): Unit = {
pool.push(conn);
}
/**
* 获取连接
*/
def getJdbcCoon(): Connection = {
//同步代码块
AnyRef.synchronized({
if (pool.isEmpty()) {
for (i <- 1 to connectionNum) {
val conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xtwy", "root", "root");
pool.push(conn);
conNum + 1;
}
}
pool.poll();
})
}
}
在Java开发特别是数据库开发中,经常会用到Class.forName( )这个方法。
通过查询Java Documentation我们会发现使用Class.forName( )静态方法的目的是为了动态加载类。
通常编码过程中,在加载完成后,一般还要调用Class下的newInstance( )静态方法来实例化对象以便操作。因此,单单使用Class.forName( )是动态加载类是没有用的,其最终目的是为了实例化对象。
有数据库开发经验朋友会发现,为什么在我们加载数据库驱动包的时候有的却没有调用newInstance( )方法呢?
即有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一 些:Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢?
刚才提到,Class.forName(“”);的作用是要求JVM查找并加载指定的类,首先要明白,java里面任何class都要装载在虚拟机上才能运行,而静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了,而且以后不会再走这段静态代码了。
而我们前面也说了,Class.forName(xxx.xx.xx)的作用就是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。
而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的 Driver类的代码都必须类似如下:强调内容
public class MyJDBCDriver implements Driver {
static {
DriverManager.registerDriver(new MyJDBCDriver());
}
}
既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了。
开发代码把统计结果存进去。
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.SparkConf
import org.apache.spark.streaming.Seconds
object ForEachRDDOperation {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("ForEachRDDOperation")
val ssc = new StreamingContext(conf, Seconds(2));
val fileDS = ssc.socketTextStream("192.168.32.110", 9999);
val wordcountDS = fileDS.flatMap { line => line.split("\t") }
.map { word => (word, 1) }
.reduceByKey(_ + _)
wordcountDS.foreachRDD(partitionOfRecords => {
partitionOfRecords.foreachPartition(records => {
val connect = MysqlPool.getJdbcCoon();
while (records.hasNext) {
val tuple = records.next();
val sql = "insert into wordcount values( now(),'" + tuple._1 + "', " + tuple._2.toInt + ")";
val statement = connect.createStatement();
statement.executeUpdate(sql);
print(sql);
}
MysqlPool.releaseConn(connect)
})
})
ssc.start()
ssc.awaitTermination()
}
}