• java8下spark-streaming结合kafka编程(spark 2.3 kafka 0.10)


    前面有说道spark-streaming的简单demo,也有说到kafka成功跑通的例子,这里就结合二者,也是常用的使用之一。

    1.相关组件版本
    首先确认版本,因为跟之前的版本有些不一样,所以才有必要记录下,另外仍然没有使用scala,使用java8,spark 2.0.0,kafka 0.10。

    2.引入maven包
    网上找了一些结合的例子,但是跟我当前版本不一样,所以根本就成功不了,所以探究了下,列出引入包。

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


    网上能找到的不带kafka版本号的包最新是1.6.3,我试过,已经无法在spark2下成功运行了,所以找到的是对应kafka0.10的版本,注意spark2.0的scala版本已经是2.11,所以包括之前必须后面跟2.11,表示scala版本。

    3.SparkSteamingKafka类
    需要注意的是引入的包路径是org.apache.spark.streaming.kafka010.xxx,所以这里把import也放进来了。其他直接看注释。

    import java.util.Arrays;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;

    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.apache.kafka.common.TopicPartition;
    import org.apache.spark.SparkConf;
    import org.apache.spark.api.java.JavaSparkContext;
    import org.apache.spark.streaming.Durations;
    import org.apache.spark.streaming.api.java.JavaInputDStream;
    import org.apache.spark.streaming.api.java.JavaPairDStream;
    import org.apache.spark.streaming.api.java.JavaStreamingContext;
    import org.apache.spark.streaming.kafka010.ConsumerStrategies;
    import org.apache.spark.streaming.kafka010.KafkaUtils;
    import org.apache.spark.streaming.kafka010.LocationStrategies;

    import scala.Tuple2;

    public class SparkSteamingKafka {
    public static void main(String[] args) throws InterruptedException {
    String brokers = "master2:6667";
    String topics = "topic1";
    SparkConf conf = new SparkConf().setMaster("local[2]").setAppName("streaming word count");
    JavaSparkContext sc = new JavaSparkContext(conf);
    sc.setLogLevel("WARN");
    JavaStreamingContext ssc = new JavaStreamingContext(sc, Durations.seconds(1));

    Collection<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
    //kafka相关参数,必要!缺了会报错
    Map<String, Object> kafkaParams = new HashMap<>();
    kafkaParams.put("metadata.broker.list", brokers) ;
    kafkaParams.put("bootstrap.servers", brokers);
    kafkaParams.put("group.id", "group1");
    kafkaParams.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    kafkaParams.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    kafkaParams.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    //Topic分区 也可以通过配置项实现
    //如果没有初始化偏移量或者当前的偏移量不存在任何服务器上,可以使用这个配置属性
    //earliest 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
    //latest 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
    //none topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
    //kafkaParams.put("auto.offset.reset", "latest");
    //kafkaParams.put("enable.auto.commit",false);

    new HashMap<>();
    offsets.put(new TopicPartition("topic1", 0), 2L);
    //通过KafkaUtils.createDirectStream(...)获得kafka数据,kafka相关参数由kafkaParams指定
    JavaInputDStream<ConsumerRecord<Object,Object>> lines = KafkaUtils.createDirectStream(
    ssc,
    LocationStrategies.PreferConsistent(),
    ConsumerStrategies.Subscribe(topicsSet, kafkaParams, offsets)
    );
    //这里就跟之前的demo一样了,只是需要注意这边的lines里的参数本身是个ConsumerRecord对象
    JavaPairDStream<String, Integer> counts =
    lines.flatMap(x -> Arrays.asList(x.value().toString().split(" ")).iterator())
    .mapToPair(x -> new Tuple2<String, Integer>(x, 1))
    .reduceByKey((x, y) -> x + y);
    counts.print();
    // 可以打印所有信息,看下ConsumerRecord的结构
    // lines.foreachRDD(rdd -> {
    // rdd.foreach(x -> {
    // System.out.println(x);
    // });
    // });
    ssc.start();
    ssc.awaitTermination();
    ssc.close();
    }
    }


    4.运行测试
    这里使用上一篇kafka初探里写的producer类,put数据到kafka服务端,我这是master2节点上部署的kafka,本地测试跑spark2。

    UserKafkaProducer producerThread = new UserKafkaProducer(KafkaProperties.topic);
    producerThread.start();

    再运行3里的SparkSteamingKafka类,可以看到已经成功。


    SparkStreaming 数据处理
    根据需要,将流式数据与Hive中的静态数据关联,结果通过Elasticsearch For Hadoop导出到ES集群中。

    如果静态数据需要定时更新,可以在创建数据流后,在foreachRDD逻辑中,根据实际情况定期更新静态数据。

    调优
    由于个人经验较少,处理的数据量不大,以下内容大多是纸上谈兵,仅供参考。

    合理的批处理时间(batchDuration)
    几乎所有的Spark Streaming调优文档都会提及批处理时间的调整,在StreamingContext初始化的时候,有一个参数便是批处理时间的设定。
    如果这个值设置的过短,即个batchDuration所产生的Job并不能在这期间完成处理,那么就会造成数据不断堆积,最终导致Spark Streaming发生阻塞。
    一般对于batchDuration的设置不会小于500ms,因为过小会导致SparkStreaming频繁的提交作业,对整个streaming造成额外的负担。
    在平时的应用中,根据不同的应用场景和硬件配置,我设在1~10s之间,我们可以根据SparkStreaming的可视化监控界面,观察Total Delay来进行batchDuration的调整,直达SparkStreaming刚刚能及时处理完上一个批处理的数据,这样就是目前情况的最优值。

    合理的Kafka拉取量(maxRatePerPartition重要)
    spark.streaming.kafka.maxRatePerPartition参数配置指定了每秒每一个topic的每一个分区获取的最大消息数。

    对于Spark Streaming消费kafka中数据的应用场景,这个配置是非常关键的。这个参数默认是没有上限的,即kafka当中有多少数据它就会直接全部拉出。而根据生产者写入Kafka的速率以及消费者本身处理数据的速度,同时这个参数需要结合上面的batchDuration,使得每个partition拉取在每个batchDuration期间拉取的数据能够顺利的处理完毕,做到尽可能高的吞吐量,而这个参数的调整可以参考可视化监控界面中的Input Rate和Processing Time。

    缓存反复使用的Dstream(RDD)
    Spark中的RDD和SparkStreaming中的Dstream,如果被反复的使用,最好利用cache(),将该数据流缓存起来,防止过度的调度资源造成的网络开销。可以参考观察Scheduling Delay参数。

    设置合理的GC
    长期使用Java的小伙伴都知道,JVM中的垃圾回收机制,可以让我们不过多的关注与内存的分配回收,更加专注于业务逻辑,JVM都会为我们搞定。对JVM有些了解的小伙伴应该知道,在Java虚拟机中,将内存分为了初生代(eden generation)、年轻代(young generation)、老年代(old generation)以及永久代(permanent generation),其中每次GC都是需要耗费一定时间的,尤其是老年代的GC回收,需要对内存碎片进行整理,通常采用标记-清楚的做法。同样的在Spark程序中,JVM GC的频率和时间也是影响整个Spark效率的关键因素。在通常的使用中建议:

    设置年老代为并发收集。
    --conf "spark.executor.extraJavaOptions=-XX:+UseConcMarkSweepGC"
    设置合理的CPU资源数
    CPU的core数量,每个executor可以占用一个或多个core,可以通过观察CPU的使用率变化来了解计算资源的使用情况,例如,很常见的一种浪费是一个executor占用了多个core,但是总的CPU使用率却不高(因为一个executor并不总能充分利用多核的能力),这个时候可以考虑让么个executor占用更少的core,同时worker下面增加更多的executor,或者一台host上面增加更多的worker来增加并行执行的executor的数量,从而增加CPU利用率。

    但是增加executor的时候需要考虑好内存消耗,因为一台机器的内存分配给越多的executor,每个executor的内存就越小,以致出现过多的数据spill over甚至out of memory的情况。

    设置合理的parallelism
    partition和parallelism,partition指的就是数据分片的数量,每一次task只能处理一个partition的数据,这个值太小了会导致每片数据量太大,导致内存压力,或者诸多executor的计算能力无法利用充分;但是如果太大了则会导致分片太多,执行效率降低。在执行action类型操作的时候(比如各种reduce操作),partition的数量会选择parent RDD中最大的那一个。而parallelism则指的是在RDD进行reduce类操作的时候,默认返回数据的paritition数量(而在进行map类操作的时候,partition数量通常取自parent RDD中较大的一个,而且也不会涉及shuffle,因此这个parallelism的参数没有影响)。所以说,这两个概念密切相关,都是涉及到数据分片的,作用方式其实是统一的。通过spark.default.parallelism可以设置默认的分片数量,而很多RDD的操作都可以指定一个partition参数来显式控制具体的分片数量。 在SparkStreaming+kafka的使用中,我们采用了Direct连接方式,前文阐述过Spark中的partition和Kafka中的Partition是一一对应的,我们一般默认设置为Kafka中Partition的数量。

    使用高性能的算子
    这里参考了美团技术团队的博文,并没有做过具体的性能测试,其建议如下:

    使用reduceByKey/aggregateByKey替代groupByKey

    使用mapPartitions替代普通map

    使用foreachPartitions替代foreach

    使用filter之后进行coalesce操作

    使用repartitionAndSortWithinPartitions替代repartition与sort类操作

    使用Kryo优化序列化性能 这个优化原则我本身也没有经过测试,但是好多优化文档有提到,这里也记录下来。 在Spark中,主要有三个地方涉及到了序列化:

    在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输。

    将自定义的类型作为RDD的泛型类型时(比如JavaRDD,Student是自定义类型),所有自定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类必须实现Serializable接口。

    使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个partition都序列化成一个大的字节数组。

    对于这三种出现序列化的地方,我们都可以通过使用Kryo序列化类库,来优化序列化和反序列化的性能。Spark默认使用的是Java的序列化机制,也就是ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。
    官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。


    ————————————————
    版权声明:本文为CSDN博主「CODE男孩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_24084925/article/details/80842534

  • 相关阅读:
    Sandcastle 这个工具生成文档不错
    Windows 服务关闭自动重启
    『录』最全前端资源汇集
    利用Continuous Testing实现Eclipse环境自动单元测试
    (转载)const指针和指向const的指针(左值右指)
    为什么寄存器比内存快?
    Vim Buffer
    Linux操作系统文件系统基础知识详解(引用内容)
    详解BOM头以及去掉BOM头的方法
    对比MySQL,什么场景MongoDB更适用
  • 原文地址:https://www.cnblogs.com/javalinux/p/15065386.html
Copyright © 2020-2023  润新知