spark steaming的性能问题

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版本才有的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值