Spark Streaming + Kafka Manager + (Kafka-spark-consumer) 组合

在之前的文章中提到了,使用 Spark Streaming + Kafka-spark-consumer 来应对Driver程序代码改变,无法从checkpoint中反序列化的问题,即其会自动将kafka的topic中,每个partition的消费offset写入到zookeeper中,当应用重新启动的时候,其可以直接从zookeeper中恢复,但是其也存在一个问题就是:Kafka Manager 无法进行监控了。


一、Kafka Manager 无法监控的原因?

Kafka Manager 在对consumer进行监控的时候,其监控的是zookeeper路径为
/consumers/<consumer.id>/offsets/<topic>/
的路径,而 Kafka-spark-consumer 使用的则是 /kafka_spark_consumer/<consumer.id>/<topic>/
目录,如下图所示:
这里写图片描述
所以Kafka Manager 无法进行监控。


二、解决办法

由于问题的出现跟 Kafka Manager 和 Kafka-spark-consumer 这两个项目有关系,那解决办法也有两个:
第一个:修改 kafka Manager 中关于 consumer 获得数据的来源方式,增加 /kafka_spark_consumer 的处理。
第二个:修改 Kafka-spark-consumer 中在写入offsets位置信息时,同时向原来 Kafka 的consumers进行写入。

以上这两种方法都要对两个开源项目进行二次开发,而我的见意是对 Kafka Manager 进行修改,原因在于 Kafka-spark-consumer 会随着项目进行发布,会非常多,维护和升级会很麻烦,由其当项目不受个人控制的时候;而Kafka Manager就不一样,一个Kafka 集群只会部署一个,那对其做改造和升级就容易多了。

说明:本次,我先对 Kafka-spark-consumer 进行二次开发,也就是上面的第二个解决方案。


三、Kafka-spark-consumer 二次开发

3.1、其实功能很简单,就是 kafka-spark-consumer 向Zookeeper中写入内容的时候,同时向 /consumers/<consumer.id>/offsets/<topic>/
写内容即可。
经过分析其主要内容在PartitionManager.java这个类中,其原始代码如下:

 //此处是将offset相关信息写入到 zookeeper 中的方法
 public void commit() {

    LOG.info("LastComitted Offset : " + _lastComittedOffset);
    LOG.info("New Emitted Offset : " + _emittedToOffset);
    LOG.info("Enqueued Offset :" + _lastEnquedOffset);

    if (_lastEnquedOffset > _lastComittedOffset) {
      //拼装某个partition写入的内容,JSON格式
      LOG.info("Committing offset for " + _partition);
      Map<Object, Object> data =
          (Map<Object, Object>) ImmutableMap.builder().put(
              "consumer",
                ImmutableMap.of("id", _ConsumerId)).put(
              "offset",
                _emittedToOffset).put("partition", _partition.partition).put(
              "broker",
                ImmutableMap.of(
                    "host",
                      _partition.host.host,
                      "port",
                      _partition.host.port)).put("topic", _topic).build();

      try {
        //执行写入操作,此处也是我们需要改动的地方
        _state.writeJSON(committedPath(), data);
        LOG.info("Wrote committed offset to ZK: " + _emittedToOffset);
        _waitingToEmit.clear();
        _lastComittedOffset = _emittedToOffset;
      } catch (Exception zkEx) {
        LOG.error("Error during commit. Let wait for refresh "
            + zkEx.getMessage());
      }

      LOG.info("Committed offset "
          + _lastComittedOffset
            + " for "
            + _partition
            + " for consumer: "
            + _ConsumerId);
      // _emittedToOffset = _lastEnquedOffset;
    } else {

      LOG.info("Last Enqueued offset "
          + _lastEnquedOffset
            + " not incremented since previous Comitted Offset "
            + _lastComittedOffset
            + " for partition  "
            + _partition
            + " for Consumer "
            + _ConsumerId
            + ". Some issue in Process!!");
    }
  }

/**
  *从名字上可以看出,是获取 kafka-spark-consumer 的 zookeeper保存路径的
*/
  private String committedPath() {
    return _stateConf.get(Config.ZOOKEEPER_CONSUMER_PATH)
        + "/"
          + _stateConf.get(Config.KAFKA_CONSUMER_ID)
          + "/"
          + _stateConf.get(Config.KAFKA_TOPIC)
          + "/"
          + _partition.getId();
  }

改动如下:

   public void commit() {

    LOG.info("LastComitted Offset : " + _lastComittedOffset);
    LOG.info("New Emitted Offset : " + _emittedToOffset);
    LOG.info("Enqueued Offset :" + _lastEnquedOffset);

    if (_lastEnquedOffset > _lastComittedOffset) {
      LOG.info("Committing offset for " + _partition);
      Map<Object, Object> data =
          (Map<Object, Object>) ImmutableMap.builder().put(
              "consumer",
                ImmutableMap.of("id", _ConsumerId)).put(
              "offset",
                _emittedToOffset).put("partition", _partition.partition).put(
              "broker",
                ImmutableMap.of(
                    "host",
                      _partition.host.host,
                      "port",
                      _partition.host.port)).put("topic", _topic).build();

      try {
        _state.writeJSON(committedPath(), data);
        //增加写入kafka的位置信息
        _state.writeBytes(kafkaConsumerCommittedPath(), _emittedToOffset.toString().getBytes());
        LOG.info("Wrote committed offset to ZK: " + _emittedToOffset);
        _waitingToEmit.clear();
        _lastComittedOffset = _emittedToOffset;
      } catch (Exception zkEx) {
        LOG.error("Error during commit. Let wait for refresh "
            + zkEx.getMessage());
      }

      LOG.info("Committed offset "
          + _lastComittedOffset
            + " for "
            + _partition
            + " for consumer: "
            + _ConsumerId);
      // _emittedToOffset = _lastEnquedOffset;
    } else {

      LOG.info("Last Enqueued offset "
          + _lastEnquedOffset
            + " not incremented since previous Comitted Offset "
            + _lastComittedOffset
            + " for partition  "
            + _partition
            + " for Consumer "
            + _ConsumerId
            + ". Some issue in Process!!");
    }
  }
  //获得kafka的consumer路径
  private String kafkaConsumerCommittedPath(){
      return "/consumers"
              + "/"
                + _stateConf.get(Config.KAFKA_CONSUMER_ID)
                + "/offsets/"
                + _stateConf.get(Config.KAFKA_TOPIC)
                + "/"
                + _partition.getId().substring(_partition.getId().lastIndexOf("_")+1);
  }

3.2、准备测试环境。
通过 kafka Manager 查看 consumers 情况:
这里写图片描述

2.4、运行 Spark Streaming + Kafka-spark-consumer 中的 Spark Streaming 程序。
注意:一定要使用自定义的class,否则是没有效果的。我的做法是,将原来jar包中的class删除了,这样就可以保存classpath下只有我们自定义的class了。

再次查看 Kafka Manager 监控程序,如下图:
这里写图片描述

从图片中可以看到,consumer 为 54321 的信息已经显示出来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spark streaming 是基于 spark 引擎的实时数据处理框架,可以通过集成 kafka 来进行数据流的处理。然而,在使用 spark streaming 进行 kafka 数据流处理时,可能会遇到一些坑。 首先,要注意 spark streamingkafka 版本的兼容性。不同版本的 spark streamingkafka 可能存在一些不兼容的问题,所以在选择版本时要特别留意。建议使用相同版本的 spark streamingkafka,以避免兼容性问题。 其次,要注意 spark streaming 的并行度设置。默认情况下,spark streaming 的并行度是根据 kafka 分区数来决定的,可以通过设置 spark streaming 的参数来调整并行度。如果并行度设置得过高,可能会导致任务处理过慢,甚至出现 OOM 的情况;而设置得过低,则可能无法充分利用集群资源。因此,需要根据实际情况进行合理的并行度设置。 另外,要注意 spark streamingkafka 的性能调优。可以通过调整 spark streaming 缓冲区的大小、批处理时间间隔、kafka 的参数等来提高性能。同时,还可以使用 spark streaming 的 checkpoint 机制来保证数据的一致性和容错性。但是,使用 checkpoint 机制可能会对性能产生一定的影响,所以需要权衡利弊。 最后,要注意处理 kafka 的消息丢失和重复消费的问题。由于网络或其他原因,可能会导致 kafka 的消息丢失;而 spark streaming 在处理数据时可能会出现重试导致消息重复消费的情况。可以通过配置合适的参数来解决这些问题,例如设置 KafkaUtils.createDirectStream 方法的参数 enable.auto.commit,并设置适当的自动提交间隔。 总之,在使用 spark streaming 进行 kafka 数据流处理时,需要留意版本兼容性、并行度设置、性能调优和消息丢失重复消费等问题,以免踩坑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值