简单谈谈——Kafka消息队列

171 阅读7分钟

kafka消息

一、Kafka基本概念

1.Kafka作用

1.1 消息系统

消息中间件,具有异步、削锋、解耦和异步通信

1.2 储存系统

Kafka将信息持久到化磁盘中,Kafka消息持久化功能和多副本机制

1.3 流式处理平台

2.Kafka 基本信息

Productor将消息发送到broker中,broker将消息进行持久化到磁盘中,Consumer 负责从broker进行订阅消息

Productor: 生产者,负责发送消息,将其投递到Kafka中

Broker: 服务代理节点,可以将broker看做是一个kafka服务

Consumer: 消费者,负责在broker进行订阅消息

Topic: 主题,Kafka消息是按照主题进行分类,生产者将消息发送到特定的主题上。而消费者进行对指定的主题的消息进行消费

Partition: 同一种主题下的分区消息是不同的,通过offset进行确保消息的顺序性,是消息在分区中的唯一标识:合理使用存储资源:可以把海量的数据按照分区进行切割一块数据存储到多台Broker中,实现负载均衡的效果。提高并行度,消费者可以以分区进行消费。

二、生产者

1.必要参数设置

bootstrap.servers:指定生产者连接的Kafka的Broker的地址清单; 指定消息的具体的编码格式: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer

2,消息的发送

2.1 发送消息的三种模式

1.发后即忘:发送消息之后,只管消息是否发送,不关心消息是否发送成功 2.同步:发送消息到RecoedAccumlator,等消息收集器中的消息在发送到Kafka集群中,在进行再次发送消息;

ProducerRecord<String, String> producerRecord = new ProducerRecord<>("zhTest",""+i, "msg" + i); try { ​ kafkaTemplate.send(producerRecord).get(); } catch (InterruptedException e) { ​ e.printStackTrace(); } catch (ExecutionException e) { ​ e.printStackTrace(); }

3.异步:发送消息到RecoedAccumlator,不用等消息收集器中的消息在发送到Kafka集群中;

# 有回调的发送
KafkaProducer<String,String> kafkaProducer=new KafkaProducer<String, String>(properties);
for (int i = 0; i < 5; i++) {
    kafkaProducer.send(new ProducerRecord<>("first", "sq" + i), new Callback() {
        @Override
        public void onCompletion(RecordMetadata metadata, Exception exception) {
            if(exception==null){
                System.out.println("主题"+metadata.topic()+"分区"+metadata.partition());
            }
        }
    });
}
# 无回调发送  
KafkaProducer<String,String> kafkaProducer=new KafkaProducer<String, String>(properties);
        for (int i = 0; i < 5; i++) {
           kafkaProducer.send(new ProducerRecord<>("first","sq"+i));
      }

2.2 消息序列化

生产者需要通过序列化器将对象转换为字节数组才能将消息发送到Kafka 消费者需要通过反序列换器将字节数组转化为对象

2.3 消息发送

消息通过send()发送到broker中,要经过拦截器/序列化器/分区器 DefaultPartitioner:默认的分区器,在默认分区器实现中,close() 方法是空方法,如果key不是null,那么默认的分区器就会对key进行哈希,最终得到的哈希值进行区分分区号。如果key和partition没有指定,那么就会通过轮询的方式进行发往主题内的各个分区。

# 自定义分区器
private final AtomicInteger counter=new AtomicInteger();
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
    List<PartitionInfo> list=cluster.partitionsForTopic(topic);
    int num=list.size();
    if(keyBytes==null){
        return counter.getAndIncrement()%num;
    }else{
        return Utils.toPositive(Utils.murmur2(keyBytes))%num;
    }
}
​
@Override
public void close() {
​
}
​
@Override
public void configure(Map<String, ?> configs) {
​
}

2.4 原理分析

主线程和sender 线程:

主线程:通过拦截器/序列化器/分区器将消息发送到消息累加器;消息累加器主要是方便消息进行批量发送,从而减少网络传输。

sender线程:将消息发送到Kafka中

2.5 重要参数设置

设置等待acks返回的机制,有三个值 0:不等待返回的acks(可能会丢数据,因为发送消息没有了失败重试机制,但是这是最低延迟):可靠性最差,效率高 1:消息发送给kafka分区中的leader后就返回(如果follower没有同步完成leader就宕机了,就会丢数据):一般传输用户的普通日志,允许丢个别数据; -1(默认):等待所有follower同步完消息后再发送(绝对不会丢数据):传输比较重要的数据 spring.kafka.producer.acks=-1

-1:生产者发送过来数据,Leader和ISR队列中的所有的节点收齐数据进行应答;

如果Follower长时间没有Leader发送应答信息,则该Follower则被踢出ISR队列,这样就不用等长时间联系不上的节点进行应答

数据完全可靠性条件=ACK级别设置为-1+分区副本大于2(至少有一个Follower)+ISR应答的最小副本两大于等于2

问题:数据重复?

幂等性:

PID:kafka每次重启都会分配一个新的;Partion:分区号,sequence:序列号,是递增的

事务:

数据有序: 默认保证同一个分区的数据是有序的,则可以设置topic只使用一个分区,这样消息就全局有序。但是降低了性能,不适合高并发情况;

productor发送消息指定需要保证顺序的几条消息送到同一个分区,这样消费者消费就是有序的。

同一张表的数据可以放到同一个分区。

数据乱序:

保证单分区消息有序

未开启幂等性:max.in.flight.requests.per.connection=1

开启幂等性:max.in.flight.requests.per.connection<=5

2.6 生产者如何提高吞吐量

.batch.size:批次大小,默认16k .linger.ms:等待时间,修改为5-100ms .compression.type:压缩为snappy RecordAccumulator:缓存区大小,修改为64M

二、消费者

2.1 消费方式:拉模式

为啥不用推模式:因为broker的发送消息效率,很难适应所有的消费者的消费速率。

2.2 消费者组

由多个consumer组成,形成一个消费组的条件,是所有消费者的groupid相同;

消费者组内的每个消费者负责消费不同分=分区的数据,一个分区只能有一个组内消费者消费;

2.3 消费者组的初始化

2.4 消费者组的详细消费流程

2.5 分区分配策略

2.5.1 Range

首先对同一个topic里面的分区按照序号进行排列,消费者也按照序号进行排列;

通过分区数/消费者数来决定每个消费者应该消费几个分区,如果除不尽,那么前面几个消费者会多消费一个分区;

容易产生数据倾斜

2.5.2 Roundrobin

轮询分配策略

2.5.3 Sticky

粘性分配策略

1.分区的分配尽可能要均匀

2.分区的分配尽可能与上次分配的保持相同

2.6 提交偏移量offset

(1) 自动提交偏移量:

Kafka中偏移量的自动提交是由参数enable_auto_commit和auto_commit_interval_ms控制的,当enable_auto_commit=True时,Kafka在消费的过程中会以频率为auto_commit_interval_ms向Kafka自带的topic(__consumer_offsets)进行偏移量提交,具体提交到哪个Partation是以算法:partation=hash(group_id)%50来计算的。

如:group_id=test_group_1,则partation=hash("test_group_1")%50=28

(2)手动提交偏移量

1.同步手动提交偏移量

同步模式下提交失败的时候一直尝试提交,直到遇到无法重试的情况下才会结束,同时同步方式下消费者线程在拉取消息会被阻塞,在broker对提交的请求做出响应之前,会一直阻塞直到偏移量提交操作成功或者在提交过程中发生异常,限制了消息的吞吐量。

2.同步手动提交偏移量

异步手动提交offset时,消费者线程不会阻塞,提交失败的时候也不会进行重试,并且可以配合回调函数在broker做出响应的时候记录错误信息。

三、Broker

3.1 broker的工作流程

四、Kafka原理

4.1 kafka写流程

1.broker进程上的leader将消息写入到本地log中

2.follower将leader上的信息拉取下来,写入到本地log中,并向leader发送Ack

3.leader接收到所有的ISR中的回复的Ack后,并向生产者返回Ack。

4.2 Kafka消费流程

1.每个Consumer都可以根据分配策略获得消费分区,

2.获取到consumer的对应offset

3.找到该分区的leader,拉取数据

4.消费者提交offset

4.3 Kafka 的数据存储形式

4.3.1 kafka 数据存储组成

1.一个topic由多个分区组成

2.一个分区由多个segment

3.一个segment是由多个文件组成(index,log,timeindex)

4.3.2 kafka数据积压

因为网络延迟导致消费失败:

重新配置消费者的超时间,将消费的超时时间改为更大点;

4.3.3 Kafka的数据清理

Kafka的消息存储在磁盘中,为了控制磁盘的占用空间,kafka需要不断的对过去的信息进行清理。

1.日志删除:按照指定策略直接删除不符合条件的日志

2.日志压缩:按照指定的key进行整合,有相同的key但是不同value值,只保存最后一个版本。

log.cleaner.enable:true 开启自动清理日志功能

log.clearnup.policy:delete: 删除日志

log.cleanup.policy:compact:压缩日志

日志删除策略:

基于时间、基于日志大小、基于日志起始偏移量的保留策略

\