栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Kafka之SpringBoot集成合集

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Kafka之SpringBoot集成合集

Kafka之SpringBoot集成

本章主要是讲解kafka基于SpringBoot集成的内容
包括一些在开发中用到的一些生产者和消费者特性
有需要的可以慢慢食用。

目录
  • Kafka之SpringBoot集成
    • 背景
    • 基本组成
      • 主题(topic)
      • 分区(partition)
      • 副本(Replica)
      • Broker
      • 消息位移(offset)
      • 消费者组(Consumers)
        • topic推给消费者的规则
      • 生产者组(Producers)
    • 环境要求
    • SpringBoot集成
      • 基本环境
        • maven:
        • yml配置
      • 生产者特性
        • 异步发送消息
        • 同步发送消息
        • 指定分区发送
        • 自定义分区规则
        • 生产者幂等性
      • 消费者特性
        • 手动提交偏移量
        • 多线程消费
        • 指定分区消费

背景

Kafka最早是由LinkedIn公司开发的,作为其自身业务消息处理的基础,后LinkedIn公司将Kafka捐赠给Apache,现在已经成为Apache的一个顶级项目了,Kafka作为一个高吞吐的分布式的消息系统,目前已经被很多公司应用在实际的业务中了,并且与许多数据处理框架相结合,比如Hadoop,Spark等。

基本组成 主题(topic)

顾名思义Topics是一些主题的集合,更通俗的说Topic就像一个消息队列,生产者可以向其写入消息,消费者可以从中读取消息,一个Topic支持多个生产者或消费者同时订阅它,所以其扩展性很好。Topic又可以由一个或多个partition(分区)组成,比如下图:

分区(partition)

其中每个partition中的消息是有序的,但相互之间的顺序就不能保证了,若Topic有多个partition,生产者的消息可以指定或者由系统根据算法分配到指定分区,若你需要所有消息都是有序的,那么你最好只用一个分区。另外partition支持消息位移读取,消息位移有消费者自身管理,比如下图:

副本(Replica)

为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。
leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader
follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的follower
在kafka中,每个主题可以有多个分区,每个分区又可以有多个副本。这多个副本中,只有一个是leader,而其他的都是follower副本。仅有leader副本可以对外提供服务

Broker

一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic

消息位移(offset)

Kafka就是用offset来表示消费者的消费进度到哪了,每个消费者会都有自己的offset。说白了offset就是表示消费者的消费进度。
在以前版本的Kafka,这个offset是由Zookeeper来管理的,后来Kafka开发者认为Zookeeper不合适大量的删改操作,于是把offset在broker以内部topic(__consumer_offsets)的方式来保存起来。
每次消费者消费的时候,都会提交这个offset,Kafka可以让你选择是自动提交还是手动提交。

消费者组(Consumers)

Consumers是一群消费者的集合,可以称之为消费者组,是一种更高层次的的抽象,向Topic订阅消费消息的单位是Consumers,当然它其中也可以只有一个消费者(consumer)。
下面是关于consumer的两条原则:
假如所有消费者都在同一个消费者组中,那么它们将协同消费订阅Topic的部分消息(根据分区与消费者的数量分配),保存负载平衡;
假如所有消费者都在不同的消费者组中,并且订阅了同个Topic,那么它们将可以消费Topic的所有消息;

topic推给消费者的规则

若消费者数小于partition数,且消费者数为一个,那么它就消费所有消息;
若消费者数小于partition数,假设消费者数为N,partition数为M,那么每个消费者能消费的分区数为M/N或M/N+1;
若消费者数等于partition数,那么每个消费者都会均等分配到一个分区的消息;
若消费者数大于partition数,则将会出现部分消费者得不到消息分区,出现空闲

生产者组(Producers)

Producers作为消息的生产者,可以自己指定将消息发布到订阅Topic中的指定分区,策略可以自己指定,比如语义或者结构类似的消息发布在同一分区,当然也可以由系统循环发布在每一个分区上。

环境要求

搭建一个kafka集群,3节点,kafka版本为2.5.0。

部署不是本章的内容

可视化工具:cmak(jdk11)

SpringBoot集成 基本环境

springboot:2.2.6.RELEASE

maven:
      
            org.springframework.kafka
            spring-kafka
      
        
            org.projectlombok
            lombok
            1.18.12
        
yml配置
spring:
  kafka:
    bootstrap-servers: ip:port
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer #消费者键序列化方式
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer #消费者value序列化方式
      group-id: test-group-id #组id
      auto-offset-reset: earliest #offset重启设置 有三个属性 earliest latest none
      enable-auto-commit: false #是否自动提交偏移量
    producer:
      retries: 3 #重试次数 
      batch-size: 16384 # 批量提交数据大小
      buffer-memory: 33554432 #缓存大小
      key-serializer: org.apache.kafka.common.serialization.StringSerializer #字符串序列化
      value-serializer: org.apache.kafka.common.serialization.StringSerializer #字符串序列化
      acks: all
      properties:
        enable.idempotence: true #开启幂等性 幂等性必须重试有次数
        #partitioner.class: com.yy.config.CustomizePartitioner #自定义分区策略
    listener:
      # 在侦听器容器中运行的线程数。
      concurrency: 5
      #listner负责ack,每调用一次,就立即commit
      ack-mode: manual_immediate
      missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
生产者特性 异步发送消息

异步发送消息主要通过对回调来判断消息是否发送成功

    public void send(Object obj) {
        String obj2String = JSONObject.toJSONString(obj);
        log.info("准备发送消息为:{}", obj2String);
        //发送消息
        ListenableFuture> future = kafkaTemplate.send(TOPIC_TEST, obj);
        future.addCallback(new ListenableFutureCallback>() {
            @Override
            public void onFailure(Throwable throwable) {
                //发送失败的处理
                log.info(TOPIC_TEST + " - 生产者 发送消息失败:" + throwable.getMessage());
            }

            @Override
            public void onSuccess(SendResult stringObjectSendResult) {
                int partition = stringObjectSendResult.getRecordMetadata().partition();
                log.info("partition==========:"+partition);
                //成功的处理
                log.info(TOPIC_TEST + " - 生产者 发送消息成功:" + stringObjectSendResult.toString());
            }
        });
    }
同步发送消息

同步发送消息主要用get()方法来阻塞发送,实现同步。

    public void sendSync(Object obj) {
        String obj2String = JSONObject.toJSONString(obj);
        log.info("准备发送消息为:{}", obj2String);
        //发送消息
        SendResult stringObjectSendResult = null;
        try {
            stringObjectSendResult = kafkaTemplate.send(TOPIC_TEST, obj).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        log.info(stringObjectSendResult.toString());
    }
指定分区发送

主要是使用:
ListenableFuture> send(String topic, Integer partition, K key, @Nullable V data)
partion即指明发送给哪个分区,当然key可以换算成某个分区,具体规则如下:

1.若发送消息时指定了分区(即自定义分区策略),则直接将消息append到指定分区;
2.若发送消息时未指定patition,但指定了key(kafka允许为每条消息设置一个key),则对key值进行hash计算,根据计算结果路由到指定分区,这种情况下可以保证同一个Key的所有消息都进入到相同的分区;
3.patition 和 key 都未指定,则使用kafka默认的分区策略,轮询选出一个 patition。

    public void sendToDirctpartition(Object obj,Integer partition) {
        String obj2String = JSONObject.toJSONString(obj);
        log.info("准备发送消息为:{}", obj2String);
        //发送消息
        String key  ="test";
        ListenableFuture> future = kafkaTemplate.send(TOPIC_TEST,partition,key,obj);
        future.addCallback(new ListenableFutureCallback>() {
            @Override
            public void onFailure(Throwable throwable) {
                //发送失败的处理
                log.info(TOPIC_TEST + " - 生产者 发送消息失败:" + throwable.getMessage());
            }

            @Override
            public void onSuccess(SendResult stringObjectSendResult) {
                int partition = stringObjectSendResult.getRecordMetadata().partition();
                log.info("partition==========:"+partition);
                //成功的处理
                log.info(TOPIC_TEST + " - 生产者 发送消息成功:" + stringObjectSendResult.toString());
            }
        });
    }
自定义分区规则

在上面的指定分区发送消息提到了分区规则,那用户是否可以自定义分区规则呢?是可以的,通过实现Partitioner接口可以配置用户自己的分区规则

@Component
public class CustomizePartitioner implements Partitioner {
 
    
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // 获取topic的分区列表
        List partitionInfoList = cluster.availablePartitionsForTopic(topic);
        int partitionCount = partitionInfoList.size();
        int auditPartition = partitionCount - 1;
        return auditPartition;
    }
 
    
    @Override
    public void close() {
        System.out.println("colse ...");
    }
 
    
    @Override
    public void configure(Map configs) {
        System.out.println("init ...");
    }
}
生产者幂等性

何为幂等性?
所谓的幂等性就是指Producer不论向server发送多少次重复数据,Server端都只会持久化一条

需要注意:
1.kafka的版本大于0.11.0
2.生成者开启尝试次数

properties:
        enable.idempotence: true #开启幂等性 幂等性必须重试有次数
消费者特性 手动提交偏移量
  • 设置消费者自动提交偏移量为false
  • MANUAL_IMMEDIATE: listner负责ack,每调用一次,就立即commit
spring:
  kafka:
    consumer:
      enable-auto-commit: false 
    listener:
      ack-mode: manual_immediate

消费端在消费完信息后记得提交偏移量

ack.acknowledge()
多线程消费
  1. 在某个消费端指明当前的线程数
    @KafkaListener注解加上concurrency 属性配置线程数
@KafkaListener(topics = KafkaProvider.TOPIC_TEST,groupId = KafkaProvider.TOPIC_GROUP,concurrency = "5")
public void topic_test(ConsumerRecord record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
    Optional message = Optional.ofNullable(record.value());
    if (message.isPresent()) {
        Object msg = message.get();
        log.info("topic_test 消费了: Topic:" + topic + ",Message:" + msg);
        ack.acknowledge();
        consumer.commitAsync();
    }
}
  1. 在yml配置直接配置全局的线程数
spring:
  kafka:
    listener:
      # 在侦听器容器中运行的线程数。
      concurrency: 5
指定分区消费
@KafkaListener(topicPartitions = {@TopicPartition(topic =KafkaProvider.TOPIC_TEST,partitions = {"0"})},groupId = KafkaProvider.TOPIC_GROUP1)
public void topic_test3(ConsumerRecord record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {

    Optional message = Optional.ofNullable(record.value());
    if (message.isPresent()) {
        Object msg = message.get();
        int partition = record.partition();
        log.info("topic_test3 消费了: Topic:" + topic + ",Message:" + msg+",partition:"+partition);
        ack.acknowledge();
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945431.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号