1.Steaming 的数据处理大致流程
Receiving(数据的接收器) —> Transforming(你写的数据处理代码) --> Outputing(把处理的数据保存起来)
那么问题来了:
1.数据的发送速率突然间急剧变化,怎么处理?
2.数据转化处理的速度每天都不一样,怎么办?
3.当写外部数据库或者HDFS文件的时候突然间速度慢了下来,怎么解决?
以上三个问题,的解决办法如下:
Backpressure(压力反馈),解决的问题:1,3
Elastic Scaling(弹性伸缩),解决的问题:2
好了不要说了,下面开始直接进入主题,怎么去根据上面两个来调优
1.控制Receiver的数量
就比如,你有多个数据接收源,比如是socket的数据源,你用一个接收器去接收,就好比,你只能吃一碗饭,给你两碗~ 因此你需要找个人来一起吃
比如:
//创建多个接收器(ReceiverInputDStream),这个接收器接收一台机器上的某个端口通过socket发送过来的数据并处理
val lines1 = ssc.socketTextStream("master", 9998, StorageLevel.MEMORY_AND_DISK_SER)
val lines2 = ssc.socketTextStream("master", 9997, StorageLevel.MEMORY_AND_DISK_SER)
// 联合起来组成一个,然后再进行处理
val lines = lines1.union(lines2)
2.控制Receiver数据块的数量
更多的文字描述参考:https://blog.csdn.net/kwu_ganymede/article/details/50577920,我就不再啰嗦啦
batchInterval : 触发批处理的时间间隔
blockInterval(spark.streaming.blockInterval(默认是200ms)
) : 将接收到的数据生成Block的时间间隔
那么,BlockRDD的分区数 = batchInterval / blockInterval
即一个Block就是RDD的一个分区,就是一个task
比如,batchInterval是2秒,而blockInterval是200ms,那么task数为10
如果task的数量太少,比一个executor的core数还少的话,那么可以减少blockInterval
blockInterval最好不要小于50ms,太小的话导致task数太多,那么launch task的时间久多了
3.Receiver接受数据的速率
permits per second 每秒允许接受的数据量
Spark Streaming默认的PPS是没有限制的
可以通过参数spark.streaming.receiver.maxRate来控制,默认是Long.Maxvalue 这个根据你的实际业务来设置
4.数据处理的并行度
1.BlockRDD的分区数
通过Receiver接受数据的特点决定
也可以自己通过repartition设置
2.ShuffleRDD的分区数
默认的分区数为spark.default.parallelism(core的大小)
通过我们自己设置决定
5.数据的序列化
两种需要序列化的数据:
1.输入数据
默认是以StorageLevel.MEMORY_AND_DISK_SER_2的形式存储在executor上的内存中
MEMORY_AND_DISK 表示是先存内存,内存不够了再存入磁盘中去,SER是代表序列化的意思,2是存两份
2.Streaming操作中产生的缓存RDD
默认是以StorageLevel.MEMORY_ONLY_SER的形式存储的内存中
一般情况下我们是使用Kryo序列化机制,因为它比Java序列化机制性能好,
通过sparkConf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)去设置
6.内存调优
1.需要内存大小
和transform类型有关系
数据存储的级别
2.GC
driver端和executor端都使用CMS垃圾收集器
原因:
CMS(Concurrent Mark Sweep)收集器
是一种以获取最短回收停顿时间为目标的收集器
通过–driver-java-options和spark.executor.extraJavaOptions来设置
7.Output性能
比如我们保存数据到数据库中去的时候:
以简单的WordCount来举个例子
// rdd 每次遍历的rdd time 每次运行的时间
wordCounts.foreachRDD { (rdd, time) =>
rdd.foreachPartition { partitionRecords => // foreachPartition 每个分区只创建一个连接
val conn = ConnectionPool.getConnection // 连接池
conn.setAutoCommit(false) // 手动的打开事物
val statement = conn.prepareStatement(s"insert into wordcount(ts, word, count) values (?, ?, ?)") // 这个wordcount表示你事先就已经创建好的
partitionRecords.zipWithIndex.foreach { case ((word, count), index) =>
statement.setLong(1, time.milliseconds)
statement.setString(2, word)
statement.setInt(3, count)
statement.addBatch() // 批处理
if (index != 0 && index % 500 == 0) { // 分批进行操作 每500条处理一次
statement.executeBatch()
conn.commit()
}
}
// 最后不足500条还是要提交
statement.executeBatch() //数据都放一起,和数据库的通信一次就行了
statement.close()
conn.commit()
conn.setAutoCommit(true)
ConnectionPool.returnConnection(conn)
}
}
这种方式的保存效率,无疑是最高的
数据库的连接池代码如下:
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionPool {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
static {
dataSource.setJdbcUrl("jdbc:mysql://master:3306/test");//设置连接数据库的URL
dataSource.setUser("root");//设置连接数据库的用户名
dataSource.setPassword("root");//设置连接数据库的密码
dataSource.setMaxPoolSize(40);//设置连接池的最大连接数
dataSource.setMinPoolSize(2);//设置连接池的最小连接数
dataSource.setInitialPoolSize(10);//设置连接池的初始连接数
dataSource.setMaxStatements(100);//设置连接池的缓存Statement的最大数
}
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void returnConnection(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
8.Backpressure(压力反馈)
一个稳定的steaming程序是:规定2S计算一次,在这2S内饰可以计算完成的,如果超出了2S还没有计算完成,那么这个程序显然是不稳定的
比如:生产过剩:
Feedback Loop(反馈循环) : 动态使得Streaming app从unstable状态回到stable状态
就比如和kafka之间做个反馈,steaming在规定时间处理不完,就会限制输入的数据量,从而使得这个程序回到稳定的状态
比如:hdfs处理过慢
如果写HDFS的时候太慢,导致处理时间变长,那么Spark Streaming会限制从Kafka中接受数据的速度
如何配置压力反馈:
配置: spark.streaming.backpressure.enabled = true
这个配置是从1.5版本开始的
9.Elastic Scaling(资源动态分配)
Spark Batch Application 动态的决定这个application中需要多少个Executors
比如:
1、当一个Executor空闲的时候,将这个Executor杀掉,比如:一个2S的程序,1S处理完成了,那么steaming就会杀死一些Executor,以此来提高处理的时间,充分的利用集群的资源
2、当task太多的时候,动态的启动Executors,比如:2S程序,2S都还处理不完,那么就会再启动一些Executors,使得这个程序变成稳定的
Streaming分配Executor的原则是比对 process time / batchInterval 的比率
比如:
如果Kafka接受到的数据的速率比backpressure限制的速率还要快,那么Spark Streaming会增加Executors来增加处理的速率
Kafka接受到的数据将保存在Kafka中等待Streaming app接受速率的调节
如何配置:
spark.streaming.dynamicAllocation.enabled = true
这个是从2.0版本才有的