【消息中间件】kafka知识点总结

一、核心概念

  • broker:节点,即集群中的一台主机。若存在多个broker,会选举出一个controller,用来访问zookeeper集群元数据实现集群管理
  • producer:生产者,即向broker发送消息的发送方
  • consumer:消费者,即从broker获取消息的接收方
  • consumer group:消费组,由多个消费者组成,同一条消息只会被组中的一个成员消费但可以被不同消费组消费
  • topic:主题,即一个消息队列
  • partition:分区,topic的物理组成,一个topic可以分为多个分区,分区可以分布在多个broker中。一个主题的一条消息只会发往一个分区
  • replica:分区副本,每个partition都有至少一个leader和若干个follower用于主备,实现高可用
  • leader:分区副本中的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 leader。
  • follower:分区副本中的“从”,实时从 leader 中同步数据,保持和 leader 数据的同步,不过不会和生产者消费者交互。leader 发生故障时,某个 follower 会成为新的 leader。
  • offset:消费者对某个分区的消费量,由group topic partition三者唯一标识。当消费者第一次连接kafka时会去读取该值,之后的时间会在自身内存中存储最新偏移量,消费者可以提交该值写入至kafka中(__consumer_offsets主题)。
    kafka架构

二、安装配置

下载地址:https://kafka.apache.org/downloads

# 下载(kafka由Scala编写,2.13为Scala版本)
wget https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.7.0/kafka_2.13-2.7.0.tgz

# 解压
tar -xzvf kafka_2.13-2.7.0.tgz

# 后台模式启动(需要先启动zookeeper)
kafka-server-start.sh -daemon /usr/local/etc/kafka/server.properties

server.properties关键配置项:

  • broker.id:集群节点的唯一标识

  • log.dirs:kafka将消息数据称为log,这个配置项就是定义消息数据所存储的目录

  • log.retention.hour:消息数据保留时间,默认为168小时,即7天

  • log.segment.bytes:消息数据分段大小,当partition中的log文件大小超出这个阈值后生成另外一个文件(segment)进行存储,默认为1GB

  • zookeeper.connect:kafka依赖zookeeper进行分布式集群管理,这个配置项定义的是kafka连接的zookeeper服务器,若是一个集群则用逗号分隔。

  • auto.create.topics.enable:当生产者往主题写消息,或者消费者从主题读消息,主题不存在时是否自动创建,默认为true

  • num.partitions:新创建的主题默认包含多少个分区

  • default.replication.factor:新创建的主题的默认副本因子,可通过num.partitions*default.replication.factor得到所有分区副本数

三、消息模型

1. 队列(点对点)模型

消费者都处在同一个消费组中。不过,同一个消费组中不能有多于分区个数的消费者同时消费,否则这将意味着某些消费者将无法得到消息。

2. 发布订阅(广播)模型

消费者处于不同的消费组中,生产者若发送一个消息,则不同消费组都能收到该消息

四、主题和分区

1. 相关概念

主题是一个逻辑上的概念,代表着某一个消息队列,而分区则是其物理上的实现,它的表现形式为以log和index为后缀的文件(存储在log.dirs目录下),前者存储数据,后者存储索引用于快速定位数据。

ISR(in-sync replicas):已同步副本,之前提到过一个分区至少有一个leader和若干个follower,而ISR就是leader维护的一个集合,里面存放着已同步或者并未落后太多的follower,当leader挂了之后,会从ISR中挑选出一个新leader。这个"并未落后太多“可以通过replica.lag.time.max.ms配置指定,默认为30秒,指的是当follower在这个时间内还未向leader发起过同步数据请求,则将该follower踢出ISR集合

LEO(log end offset):每个副本的最后一个offset

HW(high watermark):同一组分区里每个副本中最小的LEO,HW之前的数据才对消费者可见。当leader挂了,也能保证消费者消费数据一致性,当挂掉的leader作为follower恢复后需要丢弃HW之后的消息数据,然后向新leader同步数据

分区分配策略:round-robin,range(默认)和sticky。当消费组中的消费者数量发生变化时,触发分区策略。

  • RangeAssignor策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。假设n=分区数/消费者数量,m=分区数%消费者数量,那么前m个消费者每个分配n+1个分区,后面的(消费者数量-m)个消费者每个分配n个分区。

  • RoundRobinAssignor的原理是根据每个topic列出对应的消费者,然后轮转分配该topic下的分区。

  • StickyAssignor策略的目标则是(1)分区的分配要尽可能的均匀;(2)分区的分配尽可能的与上次分配的保持相同。当两者发生冲突时,第一个目标优先于第二个目标。

2. 命令行操作

# 列出所有topic
kafka-topics.sh --bootstrap-server localhost:9092 --list

# 查看某个topic的详情
kafka-topics.sh --bootstrap-server localhost:9092 --topic mytopic --describe

# 创建一个topic,副本数=partitions*replication-factor,其中有partitions个leader其余都为follower。注:replication-factor不能超过broker的数量
kafka-topics.sh --bootstrap-server localhost:9092 --create --topic mytopic --replication-factor 1 --partitions 3 

# 修改topic,不过不能修改replication-factor
kafka-topics.sh --bootstrap-server localhost:9092 --alter --topic mytopic --partitions 3 

# 删除一个topic
kafka-topics.sh --bootstrap-server localhost:9092 --topic mytopic --delete

五、生产者

生产者在将消息发送到某个Topic ,需要经过拦截器(Interceptor)、序列化器(Serializer)和分区器(Partitioner)的一系列作用之后才能发送到对应的Broker,在发往Broker之前是需要确定它所发往的分区。

1. 命令行操作

# 启动命令行生产者,发送键盘输入的消息
kafka-console-producer.sh --bootstrap-server localhost:9092 -topic mytopic

2. 关键参数

  • bootstrap.servers:生产者连接的kafka集群

  • key.serializer:key的序列化器

  • value.serializer:消息value的序列化器

  • acks:可选值:0,1,-1(all)。当为0时,生产者发送完消息后无需等待leader的确认;当为1时,生产者需要等待leader返回确认才能继续发送;当为-1或者"all"时,生产者需要得到leader和所有follower确认

  • retries:发送失败后的重试次数

  • retry.backoff.ms:失败重试的发送间隔,默认100ms

  • buffer.memory:生产者发送的消息会先放在buffer中缓存,该配置项定义缓冲区的大小

  • batch.size:当缓冲区的数据量达到这个配置项之后生产者才会分批发送数据,默认16384字节即16KB

  • linger.ms:当数据量未达到batch.size生产者等待linger.ms就会发送数据,

  • max.request.size:生产者会将多个批次的消息以一个request发送,这个配置了该请求的最大大小,默认1048576字节即1MB

3. 代码示例

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class KafkaProducerTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        Producer<String, String> producer = new KafkaProducer<>(prop);
        ProducerRecord<String, String> record = new ProducerRecord<>("mytopic", System.currentTimeMillis() + " hello from kafka client");
        producer.send(record, new Callback() {
            @Override
            public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                System.out.println(recordMetadata);
                System.out.println(e);
            }
        }).get();
        producer.close();
    }
}

4. 数据一致性问题

  • 数据重复
    当ack配置为-1(all)时会产生数据重复。该配置项需要生产者发送完一条消息后得到leader和所有follower确认之后才能继续发送,在此过程中当follower都同步完毕后,leader还没来得及发送响应给生产者就挂了的话,会导致生产者重复发送同一条消息。

  • 数据丢失
    当ack配置为0或1会产生数据丢失。当为0时,生产者发送完消息后无需等待leader的确认就会发送下一条消息,此时leader挂了数据就会丢失,可靠性较差;当为1时,生产者需要等待leader返回确认才能继续发送,但是当leader返回确认后挂了而follower又没来得及同步数据,此时就会造成数据丢失。

  • 精准一次
    可以看到当ack为0或1时实现了数据的atMostOnce语义,ack为-1(all)时实现了数据的atLeastOnce语义。kafka在atLeastOnce的基础上加入了幂等性实现了生产者会话期间内的exactlyOnce,所谓的幂等性指的是无论生产者发送多少条重复数据,kafka都只会持久化一条。可通过设置参数enable.idempotence为true开启。

六、消费者和消费组

consumer 采用 pull(拉)模式从 broker 中读取数据。push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。 它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适 当的速率消费消息。pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有 数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。

1. 命令行操作

# 启动命令行消费者,随机生成一个消费组,--from-beginning参数可从头开始消费
kafka-console-consumer.sh --bootstrap-server localhost:9092 -topic mytopic [--from-beginning]

# 查看消费组所消费主题的详情
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group spring --describe

2. 关键参数

  • bootstrap.servers:消费者连接的kafka集群
  • key.deserializer:key的反序列化器
  • value.deserializer:消息value的反序列化器
  • group.id:消费者所处消费组的id
  • auto.offset.reset:可选latest(默认),earliest或none。latest表示从最新的offset开始消费;earliest表示从头开始消费;none表示如果没有先前的offset则抛出异常
  • enable.auto.commit:是否开启自动提交,默认true
  • auto.commit.interval.ms:如果开启了自动提交,则间隔多久提交一次,默认5000ms即5s
  • max.poll.records:消费者一次最多拉取的消息数据,默认500
  • partition.assignment.strategy:使用哪个分区分配策略,可选org.apache.kafka.clients.consumer.RangeAssignor(默认),org.apache.kafka.clients.consumer.RoundRobinAssignor以及org.apache.kafka.clients.consumer.StickyAssignor

3. 代码示例

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

public class KafkaConsumerTest {
    public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        prop.put(ConsumerConfig.GROUP_ID_CONFIG, "KafkaClientConsumer");
        Consumer<String, String> consumer = new KafkaConsumer<>(prop);
        consumer.subscribe(Collections.singletonList("mytopic"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.println("topic=" + record.topic() + ",partition=" + record.partition() + ",offset=" + record.offset());
                System.out.println("key=" + record.key() + ",value=" + record.value());
                if (record.value().equals("commit")) {
                    consumer.commitSync();
                }
            }
        }
    }
}

4. 数据一致性问题

  • 数据重复
    在进行完业务逻辑处理后还未来得及提交offset就异常退出时,会触发rebalance,其他消费者读取到这个分区的offset还是旧的,因此会重复消费到那些已完成业务处理的消息数据。

  • 数据丢失
    在还未完成业务逻辑处理的时候异常退出了,但可能由于打开了自动提交(enable.auto.commit=true),在退出前提交了偏移量导致那些未完成处理的消息得不到消费了。

七、参考

https://juejin.im/post/6844903495670169607#heading-24

Kafka分区分配策略(1)——RangeAssignor

Kafka分区分配策略(2)——RoundRobinAssignor和StickyAssignor

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值