继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Kafka学习分享(3)

small_925_ant
关注TA
已关注
手记 69
粉丝 6395
获赞 157
1-消费位移的提交方式
2-多线程消费探讨

消费位移的提交方式

Kafka中消费位移的管理在工作中经常会使用,比如常见的流处理结合kafka.

对于Kafka中的分区而言,它的每条消息都有唯一的offset,用来表示消息在分区中对应的位置
对于消费者而言,它也有一个offset的概念,消费者使用offset来表示消费到分区中某个消息所在的位置。

对于消息在分区中的位置,将offset称为偏移量.
对于消费者消费到的位置,将offset称为位移,消费位移.

消费位移是我们经常需要使用的,并且需要进行持久化的操作,否则会出现重复消费等问题。
比如消费者进行了重启,如果不知道上一次消费的位移就会出现问题。

在新的消费者客户端,消费位移存储在Kafka内部主题_consumer_offsets中。消费者在消费完消息后需要执行消费位移的提交。
1-默认提交
Kafka中默认的消费位移的提交方式是自动提交,这个由消费者客户端参数enable.auto.commit配置,不配置默认是true:
       Properties properties = new Properties();
        properties.put("key.deserializer",
                "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put("value.deserializer",
                "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put("bootstrap.servers", broker);
        properties.put("group.id", groupId);

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        consumer.subscribe(Collections.singletonList(topic));
        while (true) {
            ConsumerRecords<String, String> records =
                    consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
            }
        }
自动提交位移的操作是在poll()方法里面完成的,在每次真正向服务端发起拉取请求之前会检查是否可以进行位移提交,如果可以就会提交上一次轮询的位移。

2-手动提交分为同步和异步
手动提交需要先进行配置enable.auto.commit:
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
         //消息消息       
         while (true) {
                ConsumerRecords<String, String> records = consumer.poll(1000);
                for (ConsumerRecord<String, String> record : records) {
                    //处理消息
                }
                //提交位移
                consumer.commitSync();
          }
也可以按照分区的粒度进行处理消息提交位移
      while (true) {
                ConsumerRecords<String, String> records = consumer.poll(1000);
           
                for (TopicPartition partition : records.partitions()) {
                   //分别获取每个分区的消息
                    List<ConsumerRecord<String, String>> partitionRecords =
                            records.records(partition);
                    for (ConsumerRecord<String, String> record : partitionRecords) {
                        //处理消息
                    }
                    //获取当前分区最后一条消息的消费位移,进行提交
                    long lastConsumedOffset = partitionRecords
                            .get(partitionRecords.size() - 1).offset();
                    consumer.commitSync(Collections.singletonMap(partition,
                            new OffsetAndMetadata(lastConsumedOffset + 1)));
                }
            }

上面使用的都是同步提交,异步提交的方法是commitAsync()

这里回顾上一次分享中flume里面消费者的代码:

//在一个batch中处理每一条消息,保存需要提交的消费位移
tpAndOffsetMetadata.put(new TopicPartition(message.topic(), message.partition()),
                new OffsetAndMetadata(message.offset() + 1, batchUUID));

//同步提交消费位移
if (!tpAndOffsetMetadata.isEmpty()) {
          long commitStartTime = System.nanoTime();
          consumer.commitSync(tpAndOffsetMetadata);
          long commitEndTime = System.nanoTime();
          ...
          tpAndOffsetMetadata.clear();
        }  

我们自己写代码的时候也可以多参考一些开源框架。
     

多线程消费探讨

KafkaProducer是线程安全的,但是KafkaConsumer是非线程安全的。
如果生产者发送消息的速度大于消费者处理的速度,那么就会有越来越多的消息得不到及时的消费,造成了一定的延迟,消息就会堆积,这时候其实需要一个完善的监控系统来监控消费者时候出现了消费滞后的情况,后面会继续学习监控方面的知识进行分享。
虽然KafkaConsumer是非线程安全的,但是依然可以使用多线程进行消费。

一般来说我们可以每个线程实例化一个KafkaConsumer对象,一个线程对应一个KafkaConsumer实例。一个消费线程可以消费一个或者多个分区,前提是所有的消费线程都属于同一个消费组。
这样每个线程可以按顺序消费各个分区中的消息。但是每个消费线程都维护一个独立的TCP连接,如果分区数和consumerThreadNum的值都很大,那么会造成不小的系统开销。

一般来说poll拉取消息的速度是相当快的,而整体消费的瓶颈也正是在处理消息这一块。
我们可以将处理消息的模块改为多线程的实现方式。但是消息的顺序处理比较困难

比如可以定义一个线程池:
executorService = new ThreadPoolExecutor(10, 30,
                    0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1000),
                    new ThreadPoolExecutor.CallerRunsPolicy());

每次拉取到一批消息就提交到线程池中进行处理
        while (true) {
               ConsumerRecords<String, String> records =
                            consumer.poll(Duration.ofMillis(100));
               if (!records.isEmpty()) {
                  executorService.submit(new ConsumerRecordsHandler(records));
                }
          }

对于消息顺序的处理,大家可以进行分享

扩展

Spark Streaming 经常会结合kafka使用,为了保证Spark Streaming应用挂掉之后仍然能够正确地读取数据,我们肯定需要进行offset的管理。
具体是实现方式,后续会通过学习进行分享。

后续

再均衡
Spark Streaming+kafka
打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP

热门评论

有源码吗?。。。。。。。。。1。。

查看全部评论