事件处理: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

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

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

创建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>

这里一定要注意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. kafka其他配置KafkaTopicConfiguration【创建topic】
/**
 * <h3>KafkaTopicConfiguration</h3>
 * <p>kafka配置类</p>
 *
 * @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. 生产者【通过接口控制】
/**
 * <h3>KafkaProducer</h3>
 * <p>生产者</p>
 *
 * @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. 消费者KafkaConsumer【对生产进行监听】
/**
 * <h3>KafkaConsumer</h3>
 * <p>消费者</p>
 *
 * @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. 启动类
/**
 * <h3>KafkaAppliction</h3>
 * <p>启动类</p>
 *
 * @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. 启动项目:
    (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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值