kafka
Kafka 是一个分布式的基于发布/订阅模式的消息队列。具有高性能、持久化、多副本备份、横向扩展能力,主要应用场景是:日志收集系统和消息系统。
kafka的特性:
- 同时为发布和订阅提供高吞吐量。
- 可进行持久化操作。将消息持久化到磁盘,因此可用于批量消费,
- 分布式系统,易于向外扩展。所有的 Producer、Broker 和Consumer 都会有多个,均为分布式的。并且,无需停机即可扩展机器。
- 消息被处理的状态是在 Consumer 端维护,而不是由 Broker 端维护。当失败时,能自动平衡。
- 同时支持离线数据处理和实时数据处理。
消息队列的模式
-
点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
生产者生产消息发送到Queue中,然后消息消费者从Queue中去除并且消费消息,消息被消费者消费后,Queue中不在存储,所以消费者不可能消费已经被消费到的消息,Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
-
发布/订阅模式(一对多,消费者消费数据之后不会清除消息)
生产者将消息发布到topic中,同事有多个消息消费者消费该消息,和点对点方式不同,发布到topic的消息会 被所有订阅者消费
kafka结构及概念
kafka的大体结构可以分为,消息生产者producer,kafka集群Broker cluster,消息消费者consumer,zookeeper集群
概念:
- producer:消息生产者,向kafka broker发送消息
- consumer:消息消费者,从kafka broker取消息
- consumer group;消息消费者组,有多个consumer组成,消息消费者组内每个消费者负责消费不同的分区数据,一个组内的消费者消费一个分区,消费者组之间互不影响。消费者组是逻辑上的一个订阅者
- broker :一台kafka服务器就是一个broker,一个集群由多个broker组成,broker存储着topic的数据,每个broker只存储着topic的一个partition,
- topic:主题,每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处
- partition: 分区,topic中的数据分为一个或者多个partition,每个top至少有一个partition,每个partition中的数据使用多个segment,每个 partition 是一个有序的队列,不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序
- replica: 副本,为保证集群中的某个节点发生故障时,该节点上的 partition 数据不丢失,且 kafka 仍然能够继续工作,kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,
- leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 leader。
- follwer: 每个分区多个副本中的“从”,实时从 leader 中同步数据,保持和 leader 数据的同步。leader 发生故障时,某个 follower 会成为新的 follower。
kafka安装
待续。。。
工作流程及broker的消息存储
producer写入流程:
producer采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(patition)中
1)producer先从zookeeper的 “/brokers/…/state”节点找到该partition的leader
2)producer将消息发送给该leader
3)leader将消息写入本地log
4)followers从leader pull消息,写入本地log后向leader发送ACK
5)leader收到所有ISR中的replication的ACK后,增加HW(high watermark,最后commit 的offset)并向producer发送ACK
Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向 topic的。topic 是逻辑上的概念,而 partition 是物理上的概念,每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。
我们可以看到,每个Partition中的消息都是有序的,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值。
由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。每个 segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称+分区序号。例如,first 这个 topic 有三个分区,则其对应的文件夹为 first-0,first-1,first-2
00000000000000000000.index
00000000000000000000.log
00000000000000170410.index
00000000000000170410.log
00000000000000239430.index
00000000000000239430.log
index文件和log文件以当前 segment 的第一条消息的 offset 命名
.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。
存储策略
无论消息是否被消费,kafka都会保留所有消息。有两种策略可以删除旧数据:
1)基于时间:log.retention.hours=168
2)基于大小:log.retention.bytes=1073741824
需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。
Kafka生产者端
1 分区策略
-
分区原因
- 方便在集群中扩展,每个 Partition 可以通过调整以适应它所在的机器,而一个 topic又可以有多个 Partition 组成,因此整个集群就可以适应任意大小的数据了
- 可以提高并发,因为可以以 Partition 为单位读写了
-
分区有序性
Kafka以分区作为最小的粒度,将每个分区分配给消费者组中不同的而且是唯一的消费者,并确保一个分区只属于一个消费者,即这个消费者就是这个分区的唯一读取线程。那么,只要分区的消息是有序的,消费者处理的消息顺序就有保证。每个主题有多个分区,不同的消费者处理不同的分区,所以Kafka不仅保证了消息的有序性,也做到了消费者的负载均衡。
分区的原则
1)指明 partition 的情况下,直接将指明的值直接作为 partiton 值;
2)未指定patition但指定key,通过对key的value进行hash出一个patition
3)patition和key都未指定,第一次调用时随机生成一个整数(后
面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin 算法。
2 分区可靠性保证
为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到producer 发送的数据后,都需要向 producer 发送 ack(acknowledgement 确认收到),如果producer 收到 ack,就会进行下一轮的发送,否则重新发送数据。
副本的同步策略:
方案 | 优点 | 缺点 |
---|---|---|
半数以上完成同步,就发 送 ack | 延迟低 | 选举新的 leader 时,容忍 n 台节点的故障,需要 2n+1 个副本 |
全部完成同步,才发送ack | 选举新的 leader 时,容忍 n 台节点的故障,需要n+1个副本 | 延迟高 |
kafka选择全部完成同步,才发送ack,这样节省资源,但是存在一个问题,:leader 收到数据,所有 follower 都开始同步数据,但有一个 follower,因为某种故障,迟迟不能与 leader 进行同步,那 leader 就要一直等下去,直到它完成同步,才能发送 ack。
解决办法:ISR
leader维护了一个动态的ISR(和 leader 保持同步的 follower 集合)当 ISR 中的 follower 完成数据的同步之后,leader 就会给 follower 发送 ack。如果 follower长时间未向leader同步数据 , 则该follower将被踢出ISR , 该 时 间 阈 值 由replica.lag.time.max.ms参数设定。Leader 发生故障之后,就会从 ISR 中选举新的 leader
ack应答机制:
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等 ISR 中的 follower 全部接收成功。所以 Kafka 为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置
ack | |
---|---|
0 | producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据 |
1 | producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,如果在 follower同步成功之前 leader 故障,那么将会丢失数据; |
-1(all) | producer 等待 broker 的 ack,partition 的 leader 和 follower(这里指的是ISR) 全部落盘成功后才返回 ack。但是如果在 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,那么会造成数据重复(follower同步完的时候没发ack,leader挂掉的情况)。 |
将服务器的 ACK 级别设置为-1,可以保证 Producer 到 Server 之间不会丢失数据,但是会重复数据,(At Least Once)相对的将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被发送一次,不会出现数据重复。(At Most Once )
但是对于重要的数据不能丢失也不能重复,在0.11 版本的 Kafka,引入了一项重大特性:幂等性 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。
At Least Once + 幂等性 = Exactly Once
如何实现幂等性:ack=-1 并且将 Producer 的参数中 enable.idompotence 设置为 true
开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条,但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区跨会话的 Exactly Once。
3 发生故障的处理
HW:
指的是消费者能见到的最大的 offset,ISR 队列中最小的 LEO
LEO:
指的是每个副本最大的 offset
1)follower 故障
follower 发生故障后会被临时踢出 ISR,待该 follower 恢复后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。等该 follower 的 LEO 大于等于该Partition的HW,即 follower 追上 leader 之后,就可以重新加入 ISR 了
2)leader 故障
leader 发生故障之后,会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader同步数据
注意:这只能保证副本之间的数据一致性(由hw保证),并不能保证数据不丢失或者不重复(由ack控制)。
消费者端
消费方式
消息由生产者发布到Kafka集群后,会被消费者消费。消息的消费模型有两种:推送模型(push)和拉取模型(pull)
Kafka的 consumer 采用 pull(拉)模式从 broker 中读取数据,push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息
pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。
分区分配策略
当消费者个数发生变化时触发
一个 consumer group 中有多个 consumer,一个 topic 有多个 partition,所以必然会涉及到 partition 的分配问题,即确定那个 partition 由哪个 consumer 来消费。Kafka 有两种分配策略,一是 RoundRobin,一是 Range。
-
RoundRobin
按照消费者组分配,将所有主题的分区组成 TopicAndPartition 列表,然后对 TopicAndPartition 列表按照 hashCode 进行排序,分发给每一个消费者,如果两个消费者订阅不同的主题,容易造成消息错乱,因此消费者最好只订阅一个topic
-
Range(默认)
按照单个主题划分,按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者
消费者offset的维护
由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢复后继续消费
Kafka 0.9 版本之前,consumer 默认将 offset 保存在 Zookeeper 中,从 0.9 版本开始,consumer 默认将 offset 保存在 Kafka 一个内置的 topic 中,该 topic 为**__consumer_offsets**
Zookeeper 在kafka中的作用
- 1、Broker 在 ZooKeeper 中的注册。
- 2、Topic 在 ZooKeeper 中的注册。
- 3、Consumer 在 ZooKeeper 中的注册。
- 4、Producer 负载均衡。Producer 从 Zookeeper 拉取 Topic 元数据,从而能够将消息发送负载均衡到对应 Topic 的分区中
- 5、Consumer 负载均衡。
- 6、记录消费进度 Offset 。
- 7、记录 Partition 与 Consumer 的关系。
总结:Broker、Producer、Consumer 和 Zookeeper 的交互。( 1、2、3、5 )
相应的状态存储到 Zookeeper 中。(4,6,7)
kafka 高效读写的原因
- 顺序写磁盘
Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
- 零拷贝技术
零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。
生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。