【分布式消息服务】——Kafka——基本原理

Kafka——分区存储

分区存储起源

我们都知道在Kafka中有一个partition的概念和参数配置,那么他究竟是什么意思;
topic:在Kafka中我们知道topic是为了我们便于将消息进行分类,分类以后我们的消费者就可以监听指定的topic获取消息
单机IO效率:那如果是单机的Kfaka,每次产生一个消息都会调用当前机器的IO写入设备写入消息,一台计算机我们知道他的IO写入效率(DMA)是有限的,大量的IO写入只会造成非常多的IO等待;
多机IO效率: 因此Kafka为了解决这个问题,在集群机制中实现分区存储,同一个topic下的消息可能存放于不同的机器中,极大效率的利用了多台计算机的IO效率;

分区存储策略

那么分区存储,究竟我们在生产了一个消息之后,Kafka利用什么样的策略将我们的消息放入到哪台计算机,并且能保证多台计算机的均衡呢,以不至于只写入一台计算机;
实际上,这种策略有很多种,我们可以通过生产者配置去指定
我们介绍一下有哪些策略吧

策略原理优点
轮询策略依据消息请求写人的顺序以此轮询多个区域平均一点
随机策略随机数来决定 更均衡效率高点,不必考虑顺序数字的争抢问题
键值策略指定一个key计算hashcode取余某个一个key落在一个分区

那实际上我们可以在Java的生产者配置中去指定这些策略以及可以自定义一种算法实现分区存储;

分区数据结构
在Kafka中消息是利用日志型设计存储的,也就是每个消息实际上最后会被写入一个.log的文件里面,当这个文件达到1GB(可以自己指定),然后扩容到第二个文件;每个文件都一段索引范围维护在ConcurrentSkipListMap中
在这里插入图片描述
每个log文件中存储的就是一行行的消息;
在这里插入图片描述

所以当我们利用索引去定位某个消息时
(1)首先通过ConcurrentSkipListMap定位index文件和log文件
(2)然后利用index文件的信息,去log文件中找数据(因为Kafka是利用稀疏索引,也就是范围索引),然后范围中用二分法遍历匹配id去找到自己的数据
这就是,Kafka用了二重映射+稀疏索引的方式来实现消息的定位
消息头:
(1) relativeOffset: 相对偏移量,表示消息相对于 baseOffset 的偏移量,占用 4 个字节,当前索引文件的文件名即为 baseOffset 的值。
(2) position: 物理地址,也就是消息在日志分段文件中对应的物理位置,占用 4 个字节。
(4) offset:绝对偏移量,外部提供的;(relativeOffset = offset - baseOffset);消息的索引项中没有直接使用绝对偏移量而改为只占用 4 个字节的相对偏移量,这样可以减小索引文件占用的空间,时间换空间
对比B+树查询的优势
B+树中数据有插入、更新、删除的时候都需要更新索引,还会引来“页分裂”等相对耗时的操作。Kafka中的索引文件也是顺序追加文件的操作,和B+树比起来工作量要小很多。

存储方式汇总

偏移量索引文件用来建立消息偏移量(offset)到物理地址之间的映射关系,方便快速定位消息所在的物理文件位置;
时间戳索引文件则根据指定的时间戳(timestamp)来查找对应的偏移量信息。

Kafka——副本冷备

一个分区的副本数量,可以在创建borker的时候为其指定,或者配置文件中指定,具体可以看Kafka集群搭建篇

副本节点的意义:副本就是数据的冗余备份,副本中会选举一个作为leader,其他都是follower;其中所有follower会自动同步leader的消息内容;
副本节点的局限:在Kafak中为了不产生复杂的幻读、重复读的问题,follower只作为冗余节点不对外提供服务做均衡,以保证消息的强一致性;
ISR副本集合:在Kafak中,当leader挂了以后要选举follwer成为leader;这些选举节点的信息会被保存在ISR副本中,ISR副本有一个裁决机制:就是从leader副本拉取消息,如果持续拉取速度慢于leader副本写入速度,慢于时间超过replica.lag.time.max.ms后,它就变成“非同步”副本,就会被踢出ISR副本集合中。但后面如何follower副本的速度慢慢提上来,那就又可能会重新加入ISR副本集合中了;
ISR只有leader特殊情况:当某个分区的leader挂了以后,如果想保证kafak集群分区的完整性(持续几个分区,继续保证写能力),这时候如果设置unclean.leader.election.enable参数为true;会选择其他节点的副本节点作为leader节点,但是如果原leader中有消息没被消息,则会导致消息丢失,可用性和一致性之间做了一个取舍;

Kafka——常见调优

副本分区调优
那比较无脑的确定分区数的方式就是broker机器数量的2~3倍。
–replication-factor 1 副本数量
–partitions 1 分区数量
假如每秒钟需要从主题写入和读取1GB数据
消费者1秒钟最多处理50MB的数据,那么这个时候就可以设置20-25个分,20*50=1GB
消息确认调优
acks为0:这意味着producer发送数据后,不会等待broker确认,直接发送下一条数据,性能最快
acks为1:为1意味着producer发送数据后,需要等待leader副本确认接收后,才会发送下一条数据,性能中等
acks为-1:这个代表的是all,意味着发送的消息写入所有的ISR集合中的副本(注意不是全部副本)后,才会发送下一条数据,性能最慢,但可靠性最强

Ps:还有一点值得一提,kafka有一个配置参数,min.insync.replicas,默认是1(也就是只有leader,实际生产应该调高),该属性规定了最小的ISR数。这意味着当acks为-1(即all)的时候,这个参数规定了必须写入的ISR集中的副本数,如果没达到,那么producer会产生异常。

Kafka——消费组机制

注意,topic下的一个分区只能被同一个consumer group下的一个consumer线程来消费
但一个consumer可以负责多个topic;
如果分区数量大于消费者数量则不同消费者可能负责多个分区
如果分区数量小于消费者,则可能部分消费者闲置,这是kafka将位移交给消费者后为了避免线程安全问题而设计的
消费组结构
group member:一个消费组类的成员
group leader:一个消费组的leader,负责分配partition
coodinator:协调者
消费组消费协调:由coodinator执行;
消费组位移元数据存储位置:消费组对于同一个topic的在分区内的消费进度,维护在一个特殊的topic中,就是__consumer_offsets,当然他可能不存在当前分区内,因此也是需要计算的
消费组位移元数据区域计算:Math.abs(groupID.hashCode()) % numPartitions

Kafka——消费位移

kafka对于消息数据存储的设计是采用日志+指针去设计的,不同于rabbitMQ这种用完了直接删除;Kafka消费消息是可以重演的,因为它是基于日志结构(log-based)的消息引擎,消费者在消费消息时,仅仅是从磁盘文件上读取数据而已,所以消费者不会删除消息数据。同时,由于位移数据是由消费者控制的,因此它能够很容易地修改位移的值,实现重复消费历史数据的功能
kafka的roker并不跟踪这些消息是否被消费者接收到;Kafka让消费者自身来管理消费的位移,并向消费者提供更新位移的接口,这种更新位移方式称为提交(commit)

/*
 * AUTO_COMMIT_INTERVAL_MS_CONFIG主要配置这个
 * 我们将再SpringBoot篇详细讲解
 */
    private Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, consumerKeyDeserializer);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, consumerValueDeserializer);
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);//每一批数量
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs);
        return props;
    }

消费位移:消费位移是按照消费组为单位保存了不同topic在不同分区的消费进度
位移提交:每个Consumer可能先消费签提交自己的消费位移,也可能是消费后提交,也可能是自动提交,以上报自己的消费情况,避免重复消费;

自动提交

首先,必须再配置这设置一个轮询时间,auto.commit.interval.ms默认值是50000ms。即kafka每隔5s会帮你自动提交一次位移,java应用消费消息后仅在应用内标记,然后定时提交,自动提交显然随机性太大,无法保证业务代码执行和消息提交的原子性;但是手动提交能保证立即性,所以更可靠但不是完全可靠;
重复消费:对于自身消费者来讲,在kafka中缓存了Current Position,所以即使消息不提交也不会重复消费,但是如果此时消费者挂了,自己再重启或者重平衡后其他消费者读取消费位移的依据是commit_offset内的数据,因此由于你将Current Position同步给commit_offset导致了重复消费问题
消息丢失(位移跳跃):拉取了100条后,自动提交直接帮你把Current Position同步给commit_offset了,但是你实际还没处理完业务,然后呢这个时候消费者挂了或者发生了重平衡,导致实际业务代码没执行完所以导致消息丢失,因此需要手动提交这个是最重要的原因;

手动提交

业务代码执行前立即提交,好处就是业务代码还没执行前就可以提交位移,但是可能此时消费进程被干掉了!导致消费丢失
业务代码执行后立即提交,好处之后执行的话也可以保证业务执行完迅速执行提交,但是也可能还没提交也被干掉了,手动提交这样的设计只是概率很小;所以最严谨的做法还是数据库标记
同步提交:同步提交位移Consumer 程序会处于阻塞状态,等待 Broker 返回提交结果,如果位移提交失败会死循环重试,java应用内拉取消息后,可以每消费一个提交一次;
异步提交:异步提交会立即返回,不会阻塞,因此不会影响 Consumer 应用的 TPS;由于它是异步的,Kafka 提供了回调函数,供你实现提交之后的逻辑,比如记录日志或处理异常等

其他概念

最大位移提交:多个消息批量拉取,然后最后一次性提交位移;比如拉到100,提交
的就是100;commitSync()和commitAsync()会提交上一次poll()的最大位移;自动位移提交,也是提交最大位移
指定位移消费:每处理一个消息,提交一次位移;commitSync()和commitAsync()允许我们指定特定的位移参数

Kafka——Rebalance

Group Coordinator

基本含义:Group Coordinator 是一个服务,每个 Broker在启动的时候都会启动一个该服务。
存储内容:Group Coordinator 的作用是用来存储 Group 的相关 Meta 信息,并将对应 分区 的 Offset 信息记录到 Kafka 内置Topic(__consumer_offsets) 中,以向消息的消费者说明消息位移的进度进行指针指向。
存储位置:Kafka 在 0.9 之前是基于 Zookeeper 来存储 Partition 的 Offset 信息 (consumers/{group}/offsets/{topic}/{partition}),因为 Zookeeper 并不适用于频繁的写操作,所以在 0.9 之后通过内置 Topic 的方式来记录对应 Partition 的 Offset。
Ps:所有所有消费者poll下来的数据就是存在在不同分区的,__consumer_offsets数据,__consumer_offsets是一个存储消息消费情况的特殊主题,在客户端利用位移提交来控制;

消费基准与Rebalance

消费堆积与Rebalance:消费者消费消费,肯定基于消费者个数以及主题个数这种基数以求出最平衡的算法,而不至于在大量消息产生的时候,某个broker消费堆积,因此当以下条件产生的时候,就会触发Rebalance
Rebalance细节:所有消费者代码中止,并重新poll,因为分配发生了改变
Rebalance触发条件

条件1:有新的consumer加入
条件2:旧的consumer挂了
条件3:coordinator挂了,集群选举出新的coordinator(0.10 特有的)
条件4:topic的partition新加
条件5:consumer调用unsubscrible(),取消topic的订阅

__consumer_offsets

每个消费者加入Group的时候会向这个主题注册自己的消费者信息,比如消费哪些分区,并且消费进度在何处
查询__consumer_offsets topic所有内容
0.11.0.0之前版本
bin/kafka-console-consumer.sh
–topic __consumer_offsets
–zookeeper localhost:2181 --formatter “kafka.coordinator.GroupMetadataManager$OffsetsMessageFormatter”
–consumer.config config/consumer.properties --from-beginning
0.11.0.0之后版本(含)
bin/kafka-console-consumer.sh
–topic __consumer_offsets
–zookeeper localhost:2181
–formatter “kafka.coordinator.group.GroupMetadataManager$OffsetsMessageFormatter” --consumer.config config/consumer.properties --from-beginning
获取指定consumer group的位移信息
先计算分区:Math.abs(groupID.hashCode()) % numPartitions
然后查看 0.11.0.0之前版本
bin/kafka-simple-consumer-shell.sh
–topic __consumer_offsets
–partition 11
–broker-list localhost:9092,localhost:9093,localhost:9094 --formatter “kafka.coordinator.GroupMetadataManager$OffsetsMessageFormatter”
然后查看 0.11.0.0之后版本(含)
bin/kafka-simple-consumer-shell.sh
–topic __consumer_offsets --partition 11
–broker-list localhost:9092,localhost:9093,localhost:9094 --formatter “kafka.coordinator.group.GroupMetadataManager$OffsetsMessageFormatter”

日志数据格式:[Group, Topic, Partition]::[OffsetMetadata[Offset, Metadata], CommitTime, ExpirationTime]
显然利用,在指定分区,然后用指定topic匹配,最后指定位移就可以找到消息数据

[console-consumer-46965,test,2]::[OffsetMetadata[21,NO_METADATA],CommitTime 1479092279434,ExpirationTime 1479178679434]
[console-consumer-46965,test,1]::[OffsetMetadata[21,NO_METADATA],CommitTime 1479092284246,ExpirationTime 1479178684246]
[console-consumer-46965,test,0]::[OffsetMetadata[22,NO_METADATA],CommitTime 1479092284246,ExpirationTime 1479178684246]
[console-consumer-46965,test,2]::[OffsetMetadata[21,NO_METADATA],CommitTime 1479092284246,ExpirationTime 1479178684246]
[console-consumer-46965,test,1]::[OffsetMetadata[21,NO_METADATA],CommitTime 1479092284436,ExpirationTime 1479178684436]
[console-consumer-46965,test,0]::[OffsetMetadata[22,NO_METADATA],CommitTime 1479092284436,ExpirationTime 1479178684436]
[console-consumer-46965,test,2]::[OffsetMetadata[21,NO_METADATA],CommitTime 1479092284436,ExpirationTime 1479178684436]

Kafka——常见问题

宕机——重复消费
业务代码执行了,但是位移没提交,进程被干掉了,Current Position还没同步给commitoffest导致重复消费
宕机——消息丢失
自动提交的宕机:将Current Position同步给commitoffest,但业务代码没执行就挂掉了
超时——重复消费:当消费者消费的速度很慢的时候,可能在一个session周期内还未完成,导致心跳机制检测报告出问题,诱发重平衡以及提交失败;
重平衡——重复消费
重平衡后提交会失败,因为当前分区已经属于另外有一个组内消费者成员,当然无论是否仍然获得同一个区域,你的授权分区已经过时都会导致提交失败,所以必然重复消费;在这里插入图片描述
重平衡测试:可以先开启一个test环境,在commit处打断点,然后此时开启另外一个test环境,然后提交就会诱发这个报错;
我们可以写一个重平衡监听器,才让这个提交操作在重平衡前执行;我们下一章介绍

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值