spark streaming无缝切换job之实践

spark streaming无缝切换job之实践

**方案主要内容:**通过并行运行两个job,同时保证数据不丢失和中间状态相同,并行运行自然无缝切换;最终保证的是结果计算的最终一致性。

1.该方案需要解决的问题:

  • 1.保证kafka中数据不丢失(at last once);

  • 2.对增量更新状态的保存(Redis已经做了)。

  • 3.生产中首次消费积压的数据的时候,数据量过大导致OOM的问题;

由于数据接收速度和处理速度的不匹配导致kafka中的数据没来得及消费,消息cache在了spark的executor中,停止spark时候导致数据丢失;
在这里插入图片描述
图1 数据

注释:(1)spark streaming中来不及消费的数据在下次启动时候丢失,因为原始的offset已经提交,但是实际上并没有完全有效消费,下次启动job时候消费时候自动直接跳过这部分未消费消息。(2)spark接收端一次性接受大量上个任务未消费的数据,未消费数据积压,最终导致Executor OOM或任务奔溃。

2.各种问题的应对策略

2.1保证数据不丢失

2.1.1问题描述

上面的图中已经讲解。

2.1.2 解决方法

如何确保数据无丢失?方案一共三种:


采用Direct的方式来读取kafka(版本0.10.x以上)数据,可以指定offset,但是默认不会将当前已读取的位置存储到zookeeper。所以,如果不手动存储,那么,下次执行还是从默认的位置开始读取(或者指定的位置)

①offset存入zk

当然,也可以将读取到数据的offset,人工的存储在数据库或者其它组件中,每次执行前都读取数据库,设置offset。

还有一种方式,就是将当前已读取的位置更新到zookeeper中,这样,下次读取就会从新的位置开始。

利用direct方式能很轻松的满足每条消息,最多被处理一次、最少被处理一次、只会被处理一次,当然最后的“只会被处理一次”还是有点困难的。

kafka streaming数据无丢失读取,虽然不能保证数据只能被处理一次。但是我们可以从代码逻辑方面处理,只有在将数据处理完成后再向zk中提交kafka偏移信息。就算程序发生异常退出,我们在批次每运行时都清理上次没有完成的状态即可。

②offset存入checkpoint

为什么我这里没有讲到使用checkpoint,因为checkpoint也不能完全保证数据不丢失,反而会降低streming的吞吐量。这种方案也最简单,但是效率和灵活性很差,而且需要外部分布式存储(例如hdfs);

③offset存入默认的kafka

kafka消费者在会保存其消费的进度,也就是offset,存储的位置根据选用的kafka api不同而不同。

kafka0.10.0以上的版本中使用Direct方式topic在zookeeper中,但是offset由原来的存储在zk中,改为存储在kafka的consumer中,主要原因是zk作为分布式协调服务,对频繁的读写修改支持并不好。因此,我们可以手动提交offset到kafka中,来保证消息不丢失;

本次采用方案③的方式,因为这是一种折中的方案,更加轻量级,复杂度适中,易于基于现有的kafka监控整合在一起。

已经在本地测试通过:

spark-streaming消费kafka消息不丢失(at least once),完全消费成功才会将offset提交到kafka中;

本地实验已经可以使用spark将offset手动提交到kafka中保存,保证ML的消息消费不丢失;

主要代码逻辑如下:

  stream.foreachRDD(new VoidFunction<JavaRDD<ConsumerRecord<String,String>>>() {
            @Override
            public void call(JavaRDD<ConsumerRecord<String, String>> rdd) {
                //获取偏移
                final OffsetRange[] offsetRanges = ((HasOffsetRanges) rdd.rdd()).offsetRanges();
                System.out.println("********************---------------------");
                // some time later, after outputs have completed
                ((CanCommitOffsets) stream.inputDStream()).commitAsync(offsetRanges);
            }

        });

注释:原始的是采用自动提交offset的方式(kafkaParams.put("enable.auto.commit", "true");),导致数据丢失问题(数据积压的时候,spark receiver注释接受kafka批次的缓存生成kafkaRDD,此时很多批次没来得及处理,offset已经提交;如果此时重启job导致大量kafka中的重要更新数据没有消费),改为手动提交提交offset的方式;

仍然存在的问题

只是本地测试通过,但是没有将现有的成果整合在up中,已经完成部分代码编写,但是需要黄爽(太忙没时间)协助整合部分;

(需要进一步完成…,这部分整合应该问题不大)

2.1.3 结果对比

结合前后对比,对Direct方式手动提交offset方式已经实现,保证未消费的消息的offset不会提交。

2.2 解决数据积压可能导致的OOM问题

2.2.1 现象描述:

​ 使用kafka导致数据积压严重,再次重新启动job的时候,原始积压的数据会一次性的被spark streaming全部拉取消费,最终导致原始数据很长时间消费不完甚至OOM的问题;(所以要设置控制ML接受kafka数据的速率)

2.2.2解决方法:
		 //初始化sparkConf
        SparkConf sparkConf = new SparkConf()
                .setMaster("local[1]")
                .setAppName("fct_testapp");
        //设置是否启动反压机制,系统会根据数据处理情况,自定调节数据速率
        sparkConf.set("spark.streaming.backpressure.enabled", "true")
        //首次处理减压策略: 因为首次启动JOB的时候,由于冷启动会造成内存使用太大,为了防止这种情况出现,限制首次处理的数据量:但是只是对Receiver起作用;
		//.set("spark.streaming.backpressure.initialRate", "1")
        //设置该参数,会控制每个分区中每秒拉取消息体的条数
        .set("spark.streaming.kafka.maxRatePerPartition", "1");

处理之前:

在这里插入图片描述
图2-1 重启之后一次性拉取过多,可能导致OOM问题

处理之后:

在这里插入图片描述

图2-2 限制spark接收端摄入数据速率

2.2.3结果对比:
  • 同时开启spark.streaming.backpressure.enabled == ture,自动调节数据拉取速率,但是前提是要有相应的历史经验值(该经验值来自于spark算子多次计算之后,推断出的spark的摄取kafka数据点速率,但是对首次大数据量是没有经验值,因此没有作用)
  • 相比重新启动job时候,一次性拉取过多的积压数据,设置spark.streaming.kafka.maxRatePerPartition==1可以很好一次性的大数据量为多批次的小数据量;如图2-2 所示,削减了首次冷启动的时候,摄取kafka的最大拉取速率;

其中的关于spark.streaming.kafka.maxRatePerPartition具体值的设置,可以参考https://blog.csdn.net/wangpei1949/article/details/90727805

ka的最大拉取速率;

其中的关于spark.streaming.kafka.maxRatePerPartition具体值的设置,可以参考https://blog.csdn.net/wangpei1949/article/details/90727805

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值