• 事件处理:SpringBoot+Kafka



    关于Kafka

    1. kafka官网:http://kafka.apache.org/
    2. 是一个分布式的、分区的、多副本的、多订阅者,基于zookeeper协调的分布式日志系统(也可以当做MQ系统),常用于web/nginx日志、访问日志、消息服务等等。
    3. 高吞吐量、低延迟、可扩展性、持久性、可靠性、容错性、高并发。

      【图源来自网络】每个broker是kafka服务器一个节点,每条消息是发布到topic上【topic可以理解为类别,生产者将消息发到kafka上会指定一个类别】,每个topic上有一个或多个partition分区,发送到topic上的消息会根据分区策略追加到分区的末尾【顺序写磁盘,效率高,在创建topic时可指定parition数量】。

    其他属性:
    offset: partition中的每条消息都被标记了一个序号,这个序号表示消息在partition中的偏移量,称为offset,每一条消息在partition都有唯一的offset,消息者通过指定offset来指定要消费的消息。【消费者在消费完一条消息后会递增offset,准备去消费下一条消息,但也可以将offset设成一个较小的值,重新消费一些消费过的消息。

    producer: 生产者,生产者发送消息到指定的topic下,消息再根据分配规则append到某个partition的末尾。

    consumer: 消费者,消费者从topic中消费数据。

    consumer group: 消费者组,每个consumer属于一个特定的consumer group,可为每个consumer指定consumer group,若不指定则属于默认的group。

    同一topic的一条消息只能被同一个consumer group内的一个consumer消费,但多个consumer group可同时消费这一消息。这也是kafka用来实现一个topic消息的广播和单播的手段,如果需要实现广播,一个consumer group内只放一个消费者即可,要实现单播,将所有的消费者放到同一个consumer group即可。
    用consumer group还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。

    leader: 每个partition有多个副本,其中有且仅有一个作为leader,leader会负责所有的客户端读写操作。

    follower: follower不对外提供服务,只与leader保持数据同步,如果leader失效,则选举一个follower来充当新的leader。当follower与leader挂掉、卡住或者同步太慢,leader会把这个follower从ISR列表中删除,重新创建一个follower。

    rebalance: 同一个consumer group下的多个消费者互相协调消费工作,我们这样想,一个topic分为多个分区,一个consumer group里面的所有消费者合作,一起去消费所订阅的某个topic下的所有分区(每个消费者消费部分分区),kafka会将该topic下的所有分区均匀的分配给consumer group下的每个消费者。
    rebalance表示"重平衡",consumer group内某个消费者挂掉后,其他消费者自动重新分配订阅主题分区的过程,是 Kafka 消费者端实现高可用的重要手段。

    上述参考文章:
    https://mp.weixin.qq.com/s?__biz=MzU1NDA0MDQ3MA==&mid=2247483958&idx=1&sn=dffaad318b50f875eea615bc3bdcc80c&chksm=fbe8efcfcc9f66d9ff096fbae1c2a3671f60ca4dc3e7412ebb511252e7193a46dcd4eb11aadc&scene=21


    安装kafka服务

    1. 安装环境:VMware虚拟机CentOS7
    2. 安装方式:docker下安装
    3. 关于Linux安装docker可参考文章:虚拟机Linux环境下安装docker以及docker下mysql、redis的安装
    4. 进入虚拟机,启动docker:systemctl start docker
    5. 安装zookeeper和kafka:
    su #进入root用户
    systemctl start docker #启动docker
    docker pull wurstmeister/zookeeper #安装zookeeper
    docker pull wurstmeister/kafka #安装kafka
    docker run -d --name zookeeper -p 2181:2181  -t wurstmeister/zookeeper #启动zk
    
    # 起动kafka 服务器地址换成自己虚拟机的
    docker run -d --name kafka --publish 9092:9092 --link zookeeper:zookeeper -e KAFKA_BROKER_ID=1  -e HOST_IP=192.168.33.114  -e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.33.114:9092  -e KAFKA_ADVERTISED_HOST_NAME=192.168.33.114 -e KAFKA_ADVERTISED_PORT=9082  --restart=always  -t  wurstmeister/kafka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    docker ps查看启动的服务:
    在这里插入图片描述
    启动kafka后,进入kafka容器,进入bin目录:
    在这里插入图片描述
    到bin下后,开始创建topic:
    创建两个topic:topic1、topic2,其分区卫和副本数为1。【记得将服务器地址修改成自己虚拟机的】

     ./kafka-topics.sh --create --zookeeper 192.168.33.114:2181 --replication-factor 1 --partitions 2 --topic topic1
    
    
    • 1
    • 2
    ./kafka-topics.sh --create --zookeeper 192.168.33.114:2181 --replication-factor 1 --partitions 2 --topic topic2
    
    • 1

    创建topic可由代码进行创建,不输入命令也行。在发送消息时,kafka会帮我们自动完成topic的创建工作,但这种情况下创建的topic默认只有一个分区,分区也没有副本。如果需要设置多个分区和副本需要进行配置。


    实战

    在这里插入图片描述

    1. pom.xml【spring boot依赖和kafka依赖】
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.6.10</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.kafka</groupId>
                <artifactId>spring-kafka</artifactId>
                <version>2.8.5</version>
            </dependency>
        </dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里一定要注意kafka与springboot版本,一定要一一对应。参考网址:https://spring.io/projects/spring-kafka
    在这里插入图片描述
    不然会启动不起来,报下面错误:
    在这里插入图片描述
    2. kafka配置:【application.properties】

    spring.application.name=kafka-demo
    server.port=8081
    ###########【Kafka集群】###########
    spring.kafka.bootstrap-servers=192.168.33.114:9092
    ###########【初始化生产者配置】###########
    # 重试次数
    spring.kafka.producer.retries=0
    # 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
    spring.kafka.producer.acks=1
    # 批量大小
    spring.kafka.producer.batch-size=16384
    # 提交延时
    spring.kafka.producer.properties.linger.ms=0
    # 当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
    # linger.ms为0表示每接收到一条消息就提交给kafka,这时候batch-size其实就没用了
    ​
    # 生产端缓冲区大小
    spring.kafka.producer.buffer-memory = 33554432
    # Kafka提供的序列化和反序列化类
    spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
    spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
    # 自定义分区器
    # spring.kafka.producer.properties.partitioner.class=com.felix.kafka.producer.CustomizePartitioner
    ​
    ###########【初始化消费者配置】###########
    # 默认的消费组ID
    spring.kafka.consumer.properties.group.id=defaultConsumerGroup
    # 是否自动提交offset
    spring.kafka.consumer.enable-auto-commit=true
    # 提交offset延时(接收到消息后多久提交offset)
    spring.kafka.consumer.auto.commit.interval.ms=1000
    # 当kafka中没有初始offset或offset超出范围时将自动重置offset
    # earliest:重置为分区中最小的offset;
    # latest:重置为分区中最新的offset(消费分区中新产生的数据);
    # none:只要有一个分区不存在已提交的offset,就抛出异常;
    spring.kafka.consumer.auto-offset-reset=latest
    # 消费会话超时时间(超过这个时间consumer没有发送心跳,就会触发rebalance操作)
    spring.kafka.consumer.properties.session.timeout.ms=120000
    # 消费请求超时时间
    spring.kafka.consumer.properties.request.timeout.ms=180000
    # Kafka提供的序列化和反序列化类
    spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
    spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
    # 消费端监听的topic不存在时,项目启动会报错(关掉)
    spring.kafka.listener.missing-topics-fatal=false
    # 设置批量消费
    # spring.kafka.listener.type=batch
    # 批量消费每次最多消费多少条消息
    # spring.kafka.consumer.max-poll-records=50
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    1. kafka其他配置KafkaTopicConfiguration【创建topic】
    /**
     * 

    KafkaTopicConfiguration

    *

    kafka配置类

    * * @author : he zhe * @date : 2022-08-01 17:18 **/
    @Configuration public class KafkaTopicConfiguration { /** * 创建 KafkaAmin,可以自动检测集群中是否存在topic,不存在则创建 * @return */ @Bean public KafkaAdmin kafkaAdmin() { Map<String, Object> props = new HashMap<>(); props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.33.114:9092"); return new KafkaAdmin(props); } @Bean public NewTopic newTopic() { // 创建 topic,指定 名称、分区数、副本数 return new NewTopic("hello-kafka-test-topic", 2, (short) 1); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    1. 生产者【通过接口控制】
    /**
     * 

    KafkaProducer

    *

    生产者

    * * @author : he zhe * @date : 2022-08-01 16:49 **/
    @RestController public class KafkaProducer { @Autowired private KafkaTemplate<String, Object> kafkaTemplate; /** * 发生简单信息 * @param normalMessage */ @RequestMapping(value = "/kafka/normal/{message}", method = RequestMethod.GET) public void sendMessage1(@PathVariable("message") String normalMessage) { kafkaTemplate.send("topic1", normalMessage); } /** * 异步发送:带回调的发生消息 * @param callbackMessage */ @GetMapping("/kafka/callbackOne/{message}") public void sendMessage2(@PathVariable("message") String callbackMessage) { kafkaTemplate.send("topic1", callbackMessage).addCallback(success -> { // 消息发送到的topic String topic = success.getRecordMetadata().topic(); // 消息发送到的分区 int partition = success.getRecordMetadata().partition(); // 消息在分区内的offset long offset = success.getRecordMetadata().offset(); System.out.println("发送消息成功:" + topic + "-" + partition + "-" + offset); }, failure -> { System.out.println("发送消息失败:" + failure.getMessage()); }); } /** * 事务处理 * @param message */ @GetMapping("/kafka/transaction/{message}") public void sendMessage3(@PathVariable("message") String message) { // 声明事务:operations函数报错,消息就不会发出去。 kafkaTemplate.executeInTransaction(operations -> { //数据发往kafka operations.send("topic1",message); //模拟后续业务处理发生了异常 throw new RuntimeException("fail"); }); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    1. 消费者KafkaConsumer【对生产进行监听】
    /**
     * 

    KafkaConsumer

    *

    消费者

    * * @author : he zhe * @date : 2022-08-01 16:51 **/ @Service public class KafkaConsumer { // 消费监听 @KafkaListener(topics = {"topic1"}) public void onMessage1(ConsumerRecord record){ // 消费的哪个topic、partition的消息,打印出消息内容 System.out.println("简单消费:"+record.topic()+"-"+record.partition()+"-"+record.value()); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 启动类
    /**
     * 

    KafkaAppliction

    *

    启动类

    * * @author : he zhe * @date : 2022-08-01 16:43 **/ @SpringBootApplication public class KafkaApplication { public static void main(String[] args) { SpringApplication.run(KafkaApplication.class, args); System.out.println("KafkaApplication启动成功"); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 启动项目:
      (1)访问:GET http://localhost:8081/kafka/normal/Hello【像kafka的topic1发送一条消息】
      (2)消费者监听到topic1产生了一条消息,进行消费,打印在控制台
      在这里插入图片描述
      (3)访问GET http://localhost:8081/kafka/callbackOne/Suceess 效果同上
      (4)事务处理,访问GET http://localhost:8081/kafka/transaction/transaction,会报错,需要修改下面的配置
    # 重试次数
    spring.kafka.producer.retries=3
    # 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
    spring.kafka.producer.acks=-1
    #增加配置
    spring.kafka.producer.transaction-id-prefix=tx_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再访问,我们可以在控制台看到事务处理失败,消息并没有发生成功:
    在这里插入图片描述

  • 相关阅读:
    荟味齐鲁鲁菜网站/美食网站/菜谱网站
    Mysql之备份(Mysqldump)
    基于模糊神经网络算法预测电价(Matlabd代码实现)
    C++异常
    区块链正在开启一场回归商业,融合商业的新发展
    全面总结 Vue 3.0 的新特性!手把手教你如何入门Vue3.0(适合小白的保姆级教程)【尚硅谷vuejs3.0笔记】
    PyQt5学习笔记--多线程处理、数据交互
    《第一堂棒球课》:王牌投手·棒球1号位
    【招招有效】Windows下根据进程ID杀死进程命令教程
    深度学习论文精读[9]:PSPNet
  • 原文地址:https://blog.csdn.net/qq_43466788/article/details/126058038