kafka:【2】工作原理
kafka工作流程
kafka中消息是以topic进行分类的,topic是数据的主题,生产者是生产消息,消费者是消费消息,中间都需要topic。
topic是逻辑上的概念,而物理概念的是partition,broker(服务器)中可以有一个或多个partition,一个partition对应于一个log文件,log文件用来存储生产者的数据,生产者生产的数据会不断追加到log文件的末端,且每天数据都有自己的offset(偏移量)。消费者组中的每个消费者,消费指定分区的数据,实时记录自己消费的offset,防止出错时恢复,从上次消费位置继续消费。
kafka文件存储
生产者生产的消息会不断追加到log文件末尾,这个顺序不会打乱,官网数据说,同样的磁盘,顺序写能到600M/s,而随机写只有100k/s,之所以写的这么快,是因为顺序写,省去了大量磁头寻址的时间,从而实现高效读写。
实际存储使用log文件,当然不能一直往里存,kafka采用了分片和索引机制,每个partition又分为多个segment file,每个segment对应两个文件,即.log和.index文件。
.index
:文件存储大量索引信息.log
:文件存储大量的数据
索引文件中的元数据指向对应log文件中message的物理偏移地址
kafka的分区
为什么要分区
- 易于集群扩展:分区机制使得Topic的数据可以被“切分”成多个部分,存储在不同的服务器(Broker)上。
- 存储扩展:一个Topic的数据量可能远超单个服务器的磁盘容量。通过分区,可以将Topic的数据分散到整个集群,理论上Topic的容量只受限于集群的总磁盘空间。
- 负载均衡:读写请求会被分散到持有不同分区的多个Broker上,避免了单一服务器的I/O、CPU和网络瓶颈。当集群负载增加时,我们只需向集群中添加新的Broker服务器,并将部分分区迁移过去,即可线性地扩展整个集群的处理能力。
场景举例:假设一个Topic每秒产生1GB的数据,单台服务器无法承受如此高的写入负载。如果我们将该Topic设置为10个分区,并将它们分布在10台不同的Broker上,那么每台Broker平均只需处理每秒100MB的写入请求,使得系统可以轻松应对
- 提高并发:并发体现在生产者(Producer)和消费者(Consumer)两端。
- 生产者并发写入:生产者可以同时向多个分区发送消息。例如,发往Partition-0的请求和发往Partition-1的请求可以由不同的Broker并行处理,从而极大地提高了消息的写入速度。
- 消费者并行处理:这是分区的核心优势。在同一个消费者组(Consumer Group)内,Kafka允许不同的消费者实例同时消费不同的分区。如果一个Topic有10个分区,那么一个消费者组最多可以有10个消费者实例来并行处理消息,每个消费者负责一个分区。这使得消息的处理能力可以随着消费者数量的增加而横向扩展,从而实现高吞-吐量。最大并行度=分区数量
- 提供数据冗余和高可用性:分区是Kafka实现容灾和高可用的基础单元。每个分区都可以配置多个副本(Replica),这些副本存储在不同的Broker上。
- 保证消息的顺序性:分区在提供高并发的同时,也提供了一种局部有序性的保证。
- 分区内有序:Kafka只保证在单个分区内的消息是有序的。也就是说,生产者按照A、B、C的顺序发送到同一个分区的消息,消费者也一定会按照A、B、C的顺序来消费它们。
- 分区之间无序:但是,Kafka不保证Topic级别的全局消息顺序。发往Partition-0的消息和发往Partition-1的消息,它们的消费顺序是无法保证的。
分区的原则
producer将发送的数据封装成ProducerRecord对象。有以下3中分区原则:
- 指明partition值的情况下:直接将指明的值直接作为partiton值
- 没有指明partition值,但有key的情况下:将key的hash值与topic的partition数进行取余得到partition值
- 即没有partition值,又没有key值的情况下:第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与topic可用的partition总数取余得到partition值,也就是常说的round-robin算法。
数据可靠性保证
ack
为了保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到来自producer的消息,都需要向producer发从ack(acknowledgement),以确认收到,如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。
副本同步策略:
方案 | 优点 | 缺点 |
---|---|---|
半数以上完成同步,就发送ack | 延迟低 | 选举新的leader时,容忍n台节点的故障,需要2n+1个副本 |
全部完成同步,才发送ack | 选举新的leader时,容忍n台节点故障,需要n+1个副本 | 延迟高 |
kafka选择使用第二种方案,因为第二种方案虽然延迟有些高,但对kafka影响非常小,而这样会减少服务器的数量,避免的数据大量冗余,也就是说降低了成本
ISR
采用第二种方案后,如果leader收到数据后,follower开始同步数据,但其中一个迟迟不能与leader同步,那leader就得一直等下去,直到同步完,才能发送ack。
面对这样的问题,leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。
如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。
如果Leader发生故障之后,就会从ISR中选举新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。
消费方式
消费者的消费方式采用pull(拉)的方式从broker中读取数据
push模式是消息的发送速率由broker决定,目标是尽可能以最快速度传递消息,这样会引发一个问题,即消费者可能来不及处理消息,典型的表现就是拒绝服务以及网络拥堵。
而pull方式,则是由消费者来决定消息传递的速率,但也是有不足之处,如果kafka没有数据,消费者可能会陷入循环,一直返回空数据。kafka 为了解决这个问题,消费数据时会传入一个时长参数timeout,如果没有数据可以消费,消费者会等待一段时间后再返回,这段时长就是timeout。
分区分配策略
一个consumer group中有多个consumer,一个 topic有多个partition,所以必然会涉及到partition的分配问题,即确定那个partition由哪个consumer来消费。
Kafka有两种分配策略:RoundRobin(轮询)和Rang(范围)
RoundRobin策略:
- 将所有主题的分区按字典序排序,然后通过轮询方式分配给消费者组中的各个消费者
- 这种分配方式比较均衡,能确保每个消费者获得大致相同数量的分区
- 适用于消费者处理能力相近的场景,可以实现负载均衡
Range策略:
- 按主题逐个分配,对每个主题的分区按范围划分给消费者
- 例如,某个主题有10个分区,3个消费者,则可能分配为:消费者1(0-3),消费者2(4-6),消费者3(7-9)
- 可能导致分配不均衡,特别是当分区数不能被消费者数整除时
- 但这种策略能保证同一主题的分区连续分配,有利于局部性处理
offset(偏移量)维护
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。