消息中间件—Kafka(一)基础

Kafka作为一个分布式流处理平台,广泛应用于大数据、数据集成和流计算场景。其架构包括生产者、消费者、broker、topic和partition,支持消息持久化和高可用性。消费者通过消费者组实现负载均衡,而Spring中的Kafka应用涉及自动提交和手动提交两种消费模式。此外,Kafka还支持幂等性和事务处理,确保数据一致性。
摘要由CSDN通过智能技术生成

一、Kafka使用场景

Kafka的定位:消息中间件,消息引擎,分布式实时流处理平台

  • 大数据领域:网站行为分析,日志聚合,应用监控,流数据处理在线和离线数据处理
  • 数据集成:将数据导入离线数据仓库
  • 流计算集成:与流计算引擎集成

二、Kafka架构图

在这里插入图片描述

  • 生产者发送消息给broker的时候,会设置两个条件,一个是满足消息数量时发送,一个是满足延时时间时发送,这样就可以避免一条一条发送的模式浪费时间,又可以避免消息数量始终不满足时无法发送消息。

  • 消费者不再接收broker主动发送的消息,只能主动拉取消息,这样可以避免消费者接收过多消息导致消费者崩溃,因此Kafka只有pull(拉取),没有push(推送)。
    在这里插入图片描述

  • 生产者发送消息给broker的一个topic,消费者会订阅一个topic来拉取消息。topic可以理解为一类消息的集合,起到队列的作用。消费者可以订阅多个topic,多个消费者也可以订阅一个topic

  • 当生产者发送的消息指向一个不存在的topic,那么broker会自动创建一个topci,这个需要用配置来关闭,auto.create.topics.enable=true设置为false即可关闭自动创建。
    在这里插入图片描述

  • topic可以视作queue队列,当有几十万条消息被发送到一个topic时,大量的消息被集中堆积,导致并发访问速度慢,扩展不便等问题。kafka通过分片解决了这个问题,即将一个topic中的消息分到多个partition中进行存储,在创建topic的时候,就可以指定该topic的分片数量,在这里被称之为partition(分区)。

  • 在分区文件中,消息以.log文件的形式存储,而且所有的消息都以累加的方式不断顺序增加,不会删除,因此想要追溯历史消息,只需要按照逆序查找,添加消息时,只需要按照顺序增加。

  • 在分区文件中,还有以.index文件和.timeindex文件的形式存储的索引,因为消息是有编号的,而且消息永远不删除,所以想要快速找到消息,就必须设计索引。
    在这里插入图片描述

  • 当一个broker挂掉之后,其中的数据也就永远丢失,所以kafka同样对数据做了复制备份,做到高可用。一个集群中的多个broker会通过复制topic数据来成为副本,通过设置副本数量来规定broker成为副本的数量,当副本数量为1时,就只有一个副本,也就是它本身。当副本数量少于集群中的broker数量时,任意一个broker都可能成为副本,当然,也可能不是。当副本数量等于集群中的broker数量时,一定是所有的broker都是副本,不存在一个broker存在两个副本的情况,因为没有意义,当broker宕机时数据一样全部丢失。当副本数量多于集群中的broker数量时,kafka会拒绝创建并报错,因为无法做到,做到也没有意义。

  • 在上图中,红色字体的节点为leader节点,黑色字体的节点为follower,只有leader节点才能为客户端提供读写功能,因此无法实现读写分离,但也避免了读写不一致的问题。

  • segment 段是为了避免.log文件过大对其做出的切分,log.segment.bytes可以设置每一段的大小,当超过这一大小时,就会再生成三个文件(.log,.index,.timeindex)并以偏移量作为命名以区分。
    在这里插入图片描述

  • consumer group消费者组,引入这个概念是为了当有多个消费者在同一个消费者组里,它们消费同一个topic时,一定是一个消费者消费一个partition,不可能出现一个以上的消费者消费同一个partition的情况。当消费者多于分区时,一定是一个消费者消费一个partition,多于的消费者将没有分区可以消费,而且一旦开始消费,这个消费关系就会被固定下来,这样就会让始终没有消息可以消费的消费者始终空闲。当消费者小于分区时,一个消费者可以消费多个分区。

  • 当一个新的消费组开始对topic进行消费时,不受到上述限制,可以选择从最早的消息开始消费,也可以从最晚的消息开始消费,这样就可以做到消息的重复消费
    在这里插入图片描述

  • consumer offest偏移量,当消费者消费一部分消息之后因为某些原因重启,消费者一般不希望从第一条消息开始重复消费,这样就需要记录已经消费到的消息数目,这就是偏移量的作用。每一条消息都有一个编号offest,而且从0开始计数。

  • 消费者和offest之间的关联关系被存储在一些特殊的topic中,_consumer_offest-序号,默认是50个,可以进行设置,它会将consumer group的名字hash之后对50取模,得到的余数存储到对应的序号里。
    在这里插入图片描述

在经过如上讲解之后,这张最后的总体结构图的描述就非常清晰了。

  • 共有三个broker
  • 共有两个topic:topic 0和topic 1
  • topic 0有两个分区:partition 0和partition 1,每个分区共有三个副本
  • topic 1共有一个分区:partition 0,每个分区共有三个副本
  • 红色分区为leader分区,黑色为follower分区
  • 绿色线是消息同步
  • 蓝色线是写入消息
  • 橙色线是读取消息
  • 有两个消费者组:consumer group 0和consumer group 1
  • consumer group 0消费topic 0的两个分区
  • consumer group 1消费topic 0的两个分区和topic 1的一个分区
  • consumer group 1中的consumer 0消费 topic 0的partition 0和topic 1的partition 0
  • consumer group 1中的consumer 1消费 topic 0的partition 1
  • consumer group 1中的consumer 2没有可以消费的消息

三、Kafka在Spring中的应用

1、消费端代码

一)自动提交

		Properties props= new Properties();
        //props.put("bootstrap.servers","127.0.0.1:9093,127.0.0.2:9094,127.0.0.3:9095");
        props.put("bootstrap.servers","127.0.0.1:9092");
        props.put("group.id","test-group");
        // 是否自动提交偏移量,只有commit之后才更新消费组的 offset
        props.put("enable.auto.commit","true");
        // 消费者自动提交的间隔
        props.put("auto.commit.interval.ms","1000");
        // 从最早的数据开始消费 earliest | latest | none
        props.put("auto.offset.reset","earliest");
        props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(props);
        // 订阅topic
        consumer.subscribe(Arrays.asList("mytopic"));

        try {
            while (true){
                ConsumerRecords<String,String> records=consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String,String> record:records){
                    System.out.printf("offset = %d ,key =%s, value= %s, partition= %s%n" ,record.offset(),record.key(),record.value(),record.partition());
                }
            }
        }finally {
            consumer.close();
        }

二)手动提交

		Properties props= new Properties();
        //props.put("bootstrap.servers","127.0.0.1:9093,127.0.0.2:9094,127.0.0.3:9095");
        props.put("bootstrap.servers","127.0.0.1:9092");
        props.put("group.id","test-group");
        // 是否自动提交偏移量,只有commit之后才更新消费组的 offset
        props.put("enable.auto.commit","false");
        // 消费者自动提交的间隔
        props.put("auto.commit.interval.ms","1000");
        // 从最早的数据开始消费 earliest | latest | none
        props.put("auto.offset.reset","earliest");
        props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");

        KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(props);
        // 订阅 topic
        consumer.subscribe(Arrays.asList("commit-test"));

        final int minBatchSize = 200;
        List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
        // 手动提交
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.printf("offset = %d ,key =%s, value= %s, partition= %s%n" ,record.offset(),record.key(),record.value(),record.partition());
                buffer.add(record);
            }
            if (buffer.size() >= minBatchSize) {
                // 同步提交
                consumer.commitSync();
                buffer.clear();
            }
        }

2、生产端代码

		Properties pros=new Properties();
        //props.put("bootstrap.servers","127.0.0.1:9093,127.0.0.2:9094,127.0.0.3:9095");
        props.put("bootstrap.servers","127.0.0.1:9092");
        pros.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
        pros.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
        // 0 发出去就确认 | 1 leader 落盘就确认| all(-1) 所有Follower同步完才确认
        pros.put("acks","1");
        // 异常自动重试次数
        pros.put("retries",3);
        // 多少条数据发送一次,默认16K
        pros.put("batch.size",16384);
        // 批量发送的等待时间
        pros.put("linger.ms",5);
        // 客户端缓冲区大小,默认32M,满了也会触发消息发送
        pros.put("buffer.memory",33554432);
        // 获取元数据时生产者的阻塞时间,超时后抛出异常
        pros.put("max.block.ms",3000);

        // 创建Sender线程
        Producer<String,String> producer = new KafkaProducer<String,String>(pros);

        for (int i =0 ;i<10;i++) {
            producer.send(new ProducerRecord<String,String>("mytopic",Integer.toString(i),Integer.toString(i)));
        }

        //producer.send(new ProducerRecord<String,String>("mytopic","1","1"));
        //producer.send(new ProducerRecord<String,String>("mytopic","2","2"));

        producer.close();

四、Kafka在SpringBoot中的应用

1、配置

server.port=7271
#spring.kafka.bootstrap-servers=127.0.0.1:9093,127.0.0.2:9094,127.0.0.3:9095
spring.kafka.bootstrap-servers=127.0.0.1:9092

# producer
spring.kafka.producer.retries=1
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432
spring.kafka.producer.acks=all
spring.kafka.producer.properties.linger.ms=5
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

# consumer
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=1000
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer

2、消费端代码

@Component
public class ConsumerListener {
    @KafkaListener(topics = "springboottopic",groupId = "springboottopic-group")
    public void onMessage(String msg){
        System.out.println("----收到消息:"+msg+"----");
    }
}

3、生产端代码

@Component
public class KafkaProducer {
    @Autowired
    private KafkaTemplate<String,Object> kafkaTemplate;

    public String send(@RequestParam String msg){
        kafkaTemplate.send("springboottopic", msg);
        return "ok";
    }
}

五、进阶使用

1、幂等性处理

重发消息时一定需要幂等性处理,当消息重复发送时业务不能重复进行。kafka的broker帮助消费者做出消息重复性处理。

		// 消息幂等性
        props.put("enable.idempotence", true);

当这个属性为true时,broker将判断消息的重复性,它的依据有两个:PID(producer id)和sequence number

  1. PID:生产者编码,每一个生产者都会有一个唯一编码
  2. sequence number:消息编号,当消息编号小于等于生产者当前处理编号时,证明消息重复。消息编号比当前编号大于1时,证明消息是正确的。消息编号比当前编号超过1时,证明消息有遗漏。但是这种有序性不是全局性的,只有在同一个partition上或者在生产者一次会话中消息编号才是有序的,一旦发送到不同分区或者生产者重启或停止此次会话,就会失去有序性,这样做是为了让kafka降低性能消耗。

2、事务

一)需要事务的情景

1.发送多条消息
3. 发送消息到多个topic或多个partition
4. 消费后发送消息 consume-process-produce

二)使用方式

		// 事务ID,唯一
        props.put("transactional.id", UUID.randomUUID().toString());
        Producer<String,String> producer = new KafkaProducer<String,String>(props);
        // 初始化事务
        producer.initTransactions();
        try {
            producer.beginTransaction();
            //消费和发送在同一段代码中可以使用这个方法
        	//producer.sendOffsetsToTransaction();
            producer.send(new ProducerRecord<String,String>("transaction-test","1","1"));
            producer.send(new ProducerRecord<String,String>("transaction-test","2","2"));
            // Integer i = 1/0;
            producer.send(new ProducerRecord<String,String>("transaction-test","3","3"));
            // 提交事务
            producer.commitTransaction();
        } catch (KafkaException e) {
            // 中止事务
            producer.abortTransaction();
        }

开启事务后,当程序出现异常时,消息不会提交,因此消费者不会受到任何消息,只有正常提交,才能被消费者消费。

三)实现原理

  1. 2PC:两段提交,由于需要跨分区处理事务,kafka实现的思想类似于分布式事务的实现,即两段提交,先预提交,等到所有的事务都预提交之后才真正的提交,否则就必须放弃。
  2. transaction coordinator:事务协调者,既然启用两段提交,就需要一个协调者,初始化事务是,就是为生产者匹配一个协调者来协调大家一起提交。
  3. topic__transaction_state:事务日志,所有的事务都会被日志记录,以防止协调者挂掉重启之后,找不到之前需要协调的日志,在kafka中,这个记录也是用一个特殊的topic实现,即_transaction_state
  4. transaction.id:生产者事务ID,如果生产者挂掉,就需要用唯一的事务ID来判断生产者还未处理的事务。

四)实现步骤

在这里插入图片描述

A.:生产者通过initTransactions API向Coordinator注册事务ID
B:Coordinator记录事务日志
C:生产者把消息写入目标分区
D:分区和Coordinator的交互。当事务完成后,消息的状态应该是已提交,这样消费者才能消费到

五、特性

  • 高吞吐,低延迟
  • 高伸缩性
  • 持久性,可靠性
  • 容错性
  • 高并发
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值