Kafka介绍
有两种主要的消息传递模式:点对点传递模式、发布-订阅模式。⼤部分的消息系统选⽤发布-订阅模式。Kafka就
是⼀种发布-订阅模式。
对于消息中间件,消息分推拉两种模式。Kafka只有消息的拉取,没有推送,可以通过轮询实现消息的推送。
1. Kafka在⼀个或多个可以跨越多个数据中⼼的服务器上作为集群运⾏。
2. Kafka集群中按照主题分类管理,⼀个主题可以有多个分区,⼀个分区可以有多个副本分区。
3. 每个记录由⼀个键,⼀个值和⼀个时间戳组成。
Kafka具有四个核⼼API:
1. Producer API:允许应⽤程序将记录流发布到⼀个或多个Kafka主题。
2. Consumer API:允许应⽤程序订阅⼀个或多个主题并处理为其⽣成的记录流。
3. Streams API:允许应⽤程序充当流处理器,使⽤⼀个或多个主题的输⼊流,并⽣成⼀个或多个输出主题的输出流,从⽽有效地将输⼊流转换为输出流。
4. Connector API:允许构建和运⾏将Kafka主题连接到现有应⽤程序或数据系统的可重⽤⽣产者或使⽤者。例如,关系数据库的连接器可能会捕获对表的所有更改。
Kafka优势
- ⾼吞吐量:单机每秒处理⼏⼗上百万的消息量。即使存储了许多TB的消息,它也保持稳定的性能。
- ⾼性能:单节点⽀持上千个客户端,并保证零停机和零数据丢失。
- 持久化数据存储:将消息持久化到磁盘。通过将数据持久化到硬盘以及replication防⽌数据丢失。
- 零拷⻉
- 顺序读,顺序写
- 利⽤Linux的⻚缓存
- 分布式系统,易于向外扩展。所有的Producer、Broker和Consumer都会有多个,均为分布式的。⽆需停机即可扩展机器。多个Producer、Consumer可能是不同的应⽤。
- 可靠性 - Kafka是分布式,分区,复制和容错的。
- 客户端状态维护:消息被处理的状态是在Consumer端维护,⽽不是由server端维护。当失败时能⾃动平衡。
- ⽀持online和offline的场景。
- ⽀持多种客户端语⾔。Kafka⽀持Java、.NET、PHP、Python等多种语⾔。
Kafka应⽤场景
- ⽇志收集:⼀个公司可以⽤Kafka可以收集各种服务的Log,通过Kafka以统⼀接⼝服务的⽅式开放给各种Consumer;
- 消息系统:解耦⽣产者和消费者、缓存消息等;
- ⽤户活动跟踪:Kafka经常被⽤来记录Web⽤户或者App⽤户的各种活动,如浏览⽹⻚、搜索、点击等活动,这些活动信息被各个服务器发布到Kafka的Topic中,然后消费者通过订阅这些Topic来做实时的监控分析,亦可保存到数据库;
- 运营指标:Kafka也经常⽤来记录运营监控数据。包括收集各种分布式应⽤的数据,⽣产各种操作的集中反馈,⽐如报警和报告;
- 流式处理:⽐如Spark Streaming和Storm。
基本架构
消息和批次
1. Kafka的数据单元称为消息。可以把消息看成是数据库⾥的⼀个“数据⾏”或⼀条“记录”。消息由字节数组组成。
2. 消息有键,键也是⼀个字节数组。当消息以⼀种可控的⽅式写⼊不同的分区时,会⽤到键。
3. 为了提⾼效率,消息被分批写⼊Kafka。批次就是⼀组消息,这些消息属于同⼀个主题和分区。
4. 把消息分成批次可以减少⽹络开销。批次越⼤,单位时间内处理的消息就越多,单个消息的传输时间就越⻓。批次数据 会被压缩,这样可以提升数据的传输和存储能⼒,但是需要更多的计算处理。
模式
消息模式(schema)有许多可⽤的选项,以便于理解。如JSON和XML,但是它们缺乏强类型处理能⼒。Kafka的许多开发者喜欢使⽤Apache Avro。Avro提供了⼀种紧凑的序列化格式,模式和消息体分开。当模式发⽣变化时,不需要重新⽣成代码,它还⽀持强类型和模式进化,其版本既向前兼容,也向后兼容。
数据格式的⼀致性对Kafka很重要,因为它消除了消息读写操作之间的耦合性。
主题和分区
Kafka的消息通过主题进⾏分类。主题可⽐是数据库的表或者⽂件系统⾥的⽂件夹。主题可以被分为若⼲分区,⼀个主题通过分区分布于Kafka集群中,提供了横向扩展的能⼒。
生产者和消费者
- ⽣产者创建消息。消费者消费消息。
- ⼀个消息被发布到⼀个特定的主题上。
- ⽣产者在默认情况下把消息均衡地分布到主题的所有分区上:
- 直接指定消息的分区
- 根据消息的key散列取模得出分区
- 轮询指定分区
- 消费者通过偏移量来区分已经读过的消息,从⽽消费消息。
- 消费者是消费组的⼀部分。消费组保证每个分区只能被⼀个消费者使⽤,避免重复消费。
broker和集群
⼀个独⽴的Kafka服务器称为broker。broker接收来⾃⽣产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘上的消息。单个broker可以轻松处理数千个分区以及每秒百万级的消息量。
每个集群都有⼀个broker是集群控制器(⾃动从集群的活跃成员中选举出来)。
控制器负责管理⼯作:- 将分区分配给broker
- 监控broker
集群中⼀个分区属于⼀个broker,该broker称为分区⾸领。
⼀个分区可以分配给多个broker,此时会发⽣分区复制。
分区的复制提供了消息冗余,⾼可⽤。副本分区不负责处理消息的读写。
核心概念
Producer(⽣产者创建消息)
该⻆⾊将消息发布到Kafka的topic中。broker接收到⽣产者发送的消息后,broker将该消息追加到当前⽤于追加数据的 segment ⽂件中。
⼀般情况下,⼀个消息会被发布到⼀个特定的主题上。
1. 默认情况下通过轮询把消息均衡地分布到主题的所有分区上。
2. 在某些情况下,⽣产者会把消息直接写到指定的分区。这通常是通过消息键和分区器来实现的,分区器为键⽣成⼀个散列值,并将其映射到指定的分区上。这样可以保证包含同⼀个键的消息会被写到同⼀个分区上。
3. ⽣产者也可以使⽤⾃定义的分区器,根据不同的业务规则将消息映射到分区。
Consumer(消费者读取消息)
- 消费者订阅⼀个或多个主题,并按照消息⽣成的顺序读取它们。
- 消费者通过检查消息的偏移量来区分已经读取过的消息。偏移量是另⼀种元数据,它是⼀个不断递增的整数值,在创建消息时,Kafka 会把它添加到消息⾥。在给定的分区⾥,每个消息的偏移量都是唯⼀的。消费者把每个分区最后读取的消息偏移量保存在Zookeeper 或Kafka 上,如果消费者关闭或重启,它的读取状态不会丢失。
- 消费者是消费组的⼀部分。群组保证每个分区只能被⼀个消费者使⽤。
- 如果⼀个消费者失效,消费组⾥的其他消费者可以接管失效消费者的⼯作,再平衡,分区重新分配。
使用
- 需要环境:jdk,zookeeper,kafka
- 配置/opt/kafka_2.12-1.0.2/config中的server.properties⽂件:
- 配置zookeeper地址
- 配置配置kafka存储持久化数据的⽬录路径:log.dir=/var/lagou/kafka/kafka-logs
- 配置advertised.listeners改为本机ip
- 启动kafka-server-start.sh -daemon config/server.properties
命令行
- kafka-topics.sh ⽤于管理主题(必须指定zookeeper,从其中获取主题)
# 列出现有的主题
kafka-topics.sh --list --zookeeper localhost:2181/myKafka
# 创建主题,该主题包含⼀个分区,该分区为Leader分区,它没有Follower分区副本。partitions(分区);replication-factor(副本,副本数量不能大于brokers的数量)
kafka-topics.sh --zookeeper localhost:2181/myKafka --create --topic topic_1 --partitions 1 --replication-factor 1
# 查看分区信息
kafka-topics.sh --zookeeper localhost:2181/myKafka --list
# 查看指定主题的详细信息
kafka-topics.sh --zookeeper localhost:2181/myKafka --describe --topic topic_1
# 删除指定主题
kafka-topics.sh --zookeeper localhost:2181/myKafka --delete --topic topic_1
- kafka-console-producer.sh⽤于⽣产消息:
# 开启⽣产者
kafka-console-producer.sh --topic topic_1 --broker-list localhost:9020
- kafka-console-consumer.sh⽤于消费消息:
# 开启消费者
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic topic_1
# 开启消费者⽅式⼆,从头消费,不按照偏移量消费
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic topic_1 --from-beginning
服务端参数配置
$KAFKA_HOME/config/server.properties⽂件中的配置。
- zookeeper.connect:该参数⽤于配置Kafka要连接的Zookeeper/集群的地址。
zookeeper.connect=node2:2181,node3:2181,node4:2181/myKafka
- listeners:⽤于指定当前Broker向外发布服务的地址和端⼝,与 advertised.listeners 配合,⽤于做内外⽹隔离。
# listener.security.protocol.map
# 监听器名称和安全协议的映射配置。⽐如,可以将内外⽹隔离,即使它们都使⽤SSL。
listener.security.protocol.map=INTERNAL:SSL,EXTERNAL:SSL
# 每个监听器的名称只能在map中出现⼀次。
# inter.broker.listener.name⽤于配置broker之间通信使⽤的监听器名称,该名称必须在advertised.listeners列表中。
inter.broker.listener.name=EXTERNAL
# listeners,⽤于配置broker监听的URI以及监听器名称列表,使⽤逗号隔开多个URI及监听器名称。如果监听器名称代表的不是安全协议,必须配置listener.security.protocol.map。每个监听器必须使⽤不同的⽹络端⼝
# advertised.listeners
# 需要将该地址发布到zookeeper供客户端使⽤,如果客户端使⽤的地址与listeners配置不同。可以在zookeeper的 get /myKafka/brokers/ids/<broker.id> 中找到。在IaaS环境,该条⽬的⽹络接⼝得与broker绑定的⽹络接⼝不同。如果不设置此条⽬,就使⽤listeners的配置。跟listeners不同,该条⽬不能使⽤0.0.0.0⽹络端⼝。advertised.listeners的地址必须是listeners中配置的或配置的⼀部分。
3. broker.id:该属性⽤于唯⼀标记⼀个Kafka的Broker,它的值是⼀个任意integer值。当Kafka以分布式集群运⾏的时候,尤为重要。
4. log.dir:通过该属性的值,指定Kafka在磁盘上保存消息的⽇志⽚段的⽬录。它是⼀组⽤逗号分隔的本地⽂件系统路径。
必要参数配置
属性 | 说明 |
---|---|
bootstrap.servers | ⽣产者客户端与broker集群建⽴初始连接需要的broker地址列表,由该初始连接发现Kafka集群中其他的所有broker。该地址列表不需要写全部的Kafka集群中broker的地址,但也不要写⼀个,以防该节点宕机的时候不可⽤。形式为: host1:port1,host2:port2,… . |
key.serializer | 实现了接⼝ org.apache.kafka.common.serialization.Serializer 的key序列化类。 |
value.serializer | 实现了接⼝ org.apache.kafka.common.serialization.Serializer 的value序列化类。 |
acks | 该选项控制着已发送消息的持久性。 acks=0 :⽣产者不等待broker的任何消息确认。只要将消息放到了socket的缓冲区,就认为消息已发送。不能保证服务器是否收到该消息, retries 设置也不起作⽤,因为客户端不关⼼消息是否发送失败。客户端收到的消息偏移量永远是-1。 acks=1 :leader将记录写到它本地⽇志,就响应客户端确认消息,⽽不等待follower副本的确认。如果leader确认了消息就宕机,则可能会丢失消息,因为follower副本可能还没来得及同步该消息。 acks=all :leader等待所有同步的副本确认该消息。保证了只要有⼀个同步副本存在,消息就不会丢失。这是最强的可⽤性保证。等价于 acks=-1 。默认值为1,字符串。可选值:[all, -1, 0, 1] |
compression.type | ⽣产者⽣成数据的压缩格式。默认是none(没有压缩)。允许的值: none , gzip , snappy 和 lz4 。压缩是对整个消息批次来讲的。消息批的效率也影响压缩的⽐例。消息批越⼤,压缩效率越好。字符串类型的值。默认是none。 |
retries | 设置该属性为⼀个⼤于1的值,将在消息发送失败的时候重新发送消息。该重试与客户端收到异常重新发送并⽆⼆⾄。允许重试但是不设置 max.in.flight.requests.per.connection 为1,存在消息乱序的可能,因为如果两个批次发送到同⼀个分区,第⼀个失败了重试,第⼆个成功了,则第⼀个消息批在第⼆个消息批后。int类型的值,默认:0,可选值:[0,…,2147483647] |
retry.backoff.ms | 在向⼀个指定的主题分区重发消息的时候,重试之间的等待时间。 ⽐如3次重试,每次重试之后等待该时间⻓度,再接着重试。在⼀些失败的场景,避免了密集循环的重新发送请求。 long型值,默认100。可选值:[0,…] |
retries | retries重试次数 当消息发送出现错误的时候,系统会重发消息。 跟客户端收到错误时重发⼀样。 如果设置了重试,还想保证消息的有序性,需要设置MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1 否则在重试此失败消息的时候,其他的消息可能发送成功了 |
request.timeout.ms | 客户端等待请求响应的最⼤时⻓。如果服务端响应超时,则会重发请求,除⾮达到重试次数。该设置应该⽐replica.lag.time.max.ms (a broker configuration)要⼤,以免在服务器延迟时间内重发消息。int类型值,默认:30000,可选值:[0,…] |
interceptor.classes | 在⽣产者接收到该消息,向Kafka集群传输之前,由序列化器处理之前,可以通过拦截器对消息进⾏处理。 要求拦截器类必须实现 org.apache.kafka.clients.producer.ProducerInterceptor 接⼝。 默认没有拦截器。 Map<String, Object> configs中通过List集合配置多个拦截器类名。 |
batch.size | 当多个消息发送到同⼀个分区的时候,⽣产者尝试将多个记录作为⼀个批来处理。批处理提⾼了客户端和服务器的处理效率。 该配置项以字节为单位控制默认批的⼤⼩。 所有的批⼩于等于该值。 发送给broker的请求将包含多个批次,每个分区⼀个,并包含可发送的数据。 如果该值设置的⽐较⼩,会限制吞吐量(设置为0会完全禁⽤批处理)。如果设置的很⼤,⼜有⼀点浪费内存,因为Kafka会永远分配这么⼤的内存来参与到消息的批整合中。 |
client.id | ⽣产者发送请求的时候传递给broker的id字符串。 ⽤于在broker的请求⽇志中追踪什么应⽤发送了什么消息。 ⼀般该id是跟业务有关的字符串。 |
compression.type | ⽣产者发送的所有数据的压缩⽅式。默认是none,也就是不压缩。 ⽀持的值:none、gzip、snappy和lz4。 压缩是对于整个批来讲的,所以批处理的效率也会影响到压缩的⽐例。 |
send.buffer.bytes | TCP发送数据的时候使⽤的缓冲区(SO_SNDBUF)⼤⼩。如果设置为0,则使⽤操作系统默认的。 |
buffer.memory | ⽣产者可以⽤来缓存等待发送到服务器的记录的总内存字节。如果记录的发送速度超过了将记录发送到服务器的速度,则⽣产者将阻塞 max.block.ms 的时间,此后它将引发异常。此设置应⼤致对应于⽣产者将使⽤的总内存,但并⾮⽣产者使⽤的所有内存都⽤于缓冲。⼀些额外的内存将⽤于压缩(如果启⽤了压缩)以及维护运⾏中的请求。long型数据。默认值:33554432,可选值:[0,…] |
connections.max.idle.ms | 当连接空闲时间达到这个值,就关闭连接。long型数据,默认:540000 |
linger.ms | ⽣产者在发送请求传输间隔会对需要发送的消息进⾏累积,然后作为⼀个批次发送。⼀般情况是消息的发送的速度⽐消息累积的速度慢。有时客户端需要减少请求的次数,即使是在发送负载不⼤的情况下。该配置设置了⼀个延迟,⽣产者不会⽴即将消息发送到broker,⽽是等待这么⼀段时间以累积消息,然后将这段时间之内的消息作为⼀个批次发送。该设置是批处理的另⼀个上限:⼀旦批消息达到了 batch.size 指定的值,消息批会⽴即发送,如果积累的消息字节数达不到 batch.size 的值,可以设置该毫秒值,等待这么⻓时间之后,也会发送消息批。该属性默认值是0(没有延迟)。如果设置 linger.ms=5 ,则在⼀个请求发送之前先等待5ms。long型值,默认:0,可选值:[0,…] |
max.block.ms | 控制 KafkaProducer.send() 和 KafkaProducer.partitionsFor() 阻塞的时⻓。当缓存满了或元数据不可⽤的时候,这些⽅法阻塞。在⽤户提供的序列化器和分区器的阻塞时间不计⼊。long型值,默认:60000,可选值:[0,…] |
max.request.size | 单个请求的最⼤字节数。该设置会限制单个请求中消息批的消息个数,以免单个请求发送太多的数据。服务器有⾃⼰的限制批⼤⼩的设置,与该配置可能不⼀样。int类型值,默认1048576,可选值:[0,…] |
partitioner.class | 实现了接⼝ org.apache.kafka.clients.producer.Partitioner 的分区器实现类。默认值为:org.apache.kafka.clients.producer.internals.DefaultPartitioner |
receive.buffer.bytes | TCP接收缓存(SO_RCVBUF),如果设置为-1,则使⽤操作系统默认的值。int类型值,默认32768,可选值:[-1,…] |
security.protocol | 跟broker通信的协议:PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL. string类型值,默认:PLAINTEXT |
max.in.flight.requests.per.connection | 单个连接上未确认请求的最⼤数量。达到这个数量,客户端阻塞。如果该值⼤于1,且存在失败的请求,在重试的时候消息顺序不能保证。 int类型值,默认5。可选值:[1,…],可以保证同一个分区数据重试后的顺序 |
reconnect.backoff.max.ms | 对于每个连续的连接失败,每台主机的退避将成倍增加,直⾄达到此最⼤值。在计算退避增量之后,添加20%的随机抖动以避免连接⻛暴。 long型值,默认1000,可选值:[0,…] |
reconnect.backoff.ms | 尝试重连指定主机的基础等待时间。避免了到该主机的密集重连。该退避时间应⽤于该客户端到broker的所有连接。<>long型值,默认50。可选值:[0,…] |
序列化器
由于Kafka中的数据都是字节数组,在将消息发送到Kafka之前需要先将数据序列化为字节数组。序列化器的作⽤就是⽤于序列化要发送的消息的。
Kafka使⽤ org.apache.kafka.common.serialization.Serializer 接⼝⽤于定义序列化器,将泛型指定类型的数据转换为字节数组。
分区器
默认(DefaultPartitioner)分区计算:
1. 如果record提供了分区号,则使⽤record提供的分区号
2. 如果record没有提供分区号,则使⽤key的序列化后的值的hash值对分区数量取模
3. 如果record没有提供分区号,也没有提供key,则使⽤轮询的⽅式分配分区号
如果要⾃定义分区器,则需要
1. ⾸先开发Partitioner接⼝的实现类
2. 在KafkaProducer中进⾏设置:configs.put(“partitioner.class”, “xxx.xx.Xxx.class”)
拦截器
Producer拦截器(interceptor)和Consumer端Interceptor是在Kafka 0.10版本被引⼊的,主要⽤于实现Client端的定制化控制逻辑。
对于Producer⽽⾔,Interceptor使得⽤户在消息发送前以及Producer回调逻辑前有机会对消息做⼀些定制化需求,⽐如修改消息等。同时,Producer允许⽤户指定多个Interceptor按序作⽤于同⼀条消息从⽽形成⼀个拦截链(interceptor chain)。Intercetpor的实现接⼝是org.apache.kafka.clients.producer.ProducerInterceptor,其定义的⽅法包括:
- onSend(ProducerRecord):该⽅法封装进KafkaProducer.send⽅法中,即运⾏在⽤户主线程中。Producer确保在消息被序列化以计算分区前调⽤该⽅法。⽤户可以在该⽅法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区,否则会影响⽬标分区的计算。
- onAcknowledgement(RecordMetadata, Exception):该⽅法会在消息被应答之前或消息发送失败时调⽤,并且通常都是在Producer回调逻辑触发之前。onAcknowledgement运⾏在Producer的IO线程中,因此不要在该⽅法中放⼊很重的逻辑,否则会拖慢Producer的消息发送效率。
- close:关闭Interceptor,主要⽤于执⾏⼀些资源清理⼯作。
如前所述,Interceptor可能被运⾏在多个线程中,因此在具体实现时⽤户需要⾃⾏确保线程安全。另外倘若指定
了多个Interceptor,则Producer将按照指定顺序调⽤它们,并仅仅是捕获每个Interceptor可能抛出的异常记录到错误
⽇志中⽽⾮在向上传递。这在使⽤过程中要特别留意。
⾃定义拦截器:
1. 实现ProducerInterceptor接⼝
2. 在KafkaProducer的设置中设置⾃定义的拦截器
消费组管理
-
什么是消费者组
三个特性:
1. 消费组有⼀个或多个消费者,消费者可以是⼀个进程,也可以是⼀个线程
2. group.id是⼀个字符串,唯⼀标识⼀个消费组
3. 消费组订阅的主题每个分区只能分配给消费组⼀个消费者。 -
消费者位移(consumer position)
消费者在消费的过程中记录已消费的数据,即消费位移(offset)信息。
Kafka默认定期⾃动提交位移( enable.auto.commit = true ),也⼿动提交位移。另外kafka会定期把group消
费情况保存起来,做成⼀个offset map.位移提交
位移是提交到Kafka中的 __consumer_offsets 主题。 __consumer_offsets 中的消息保存了每个消费组某⼀时
刻提交的offset信息。
kafka-console-consumer.sh --topic __consumer_offsets --bootstrap-server node1:9092 --formatter -
再均衡
再均衡(Rebalance)本质上是⼀种协议,规定了⼀个消费组中所有消费者如何达成⼀致来分配订阅主题的每个
分区。
再均衡的触发条件:
注意:再均衡期间kafka不可用- 组成员发⽣变更(新消费者加⼊消费组组、已有消费者主动离开或崩溃了)
- 订阅主题数发⽣变更。如果正则表达式进⾏订阅,则新建匹配正则表达式的主题触发再均衡。
- 订阅主题的分区数发⽣变更
如何进⾏组内分区分配?
三种分配策略:RangeAssignor和RoundRobinAssignor以及StickyAssignor。
主题管理
偏移量管理
- 查看有那些 group ID 正在进⾏消费: kafka-consumer-groups.sh --bootstrap-server node1:9092 --list
- 查看指定group.id 的消费者消费情况:kafka-consumer-groups.sh --bootstrap-server node1:9092 --describe --group group
分区
- Kafka在⼀定数量的服务器上对主题分区进⾏复制,当集群中的⼀个broker宕机后系统可以⾃动故障转移到其他可⽤的副本上,不会造成数据丢失。
- Follower分区像普通的Kafka消费者⼀样,消费来⾃Leader分区的消息,并将其持久化到⾃⼰的⽇志中。
允许Follower对⽇志条⽬拉取进⾏批处理。 - 宕机如何恢复
- 少部分副本宕机
当leader宕机了,会从follower选择⼀个作为leader。当宕机的重新恢复时,会把之前commit的数据清空,重新从leader⾥pull数据 - 全部副本宕机
- 等待ISR中的⼀个恢复后,并选它作为leader。(等待时间较⻓,降低可⽤性)
- 选择第⼀个恢复的副本作为新的leader,⽆论是否在ISR中。(并未包含之前leader commit的数据,因此造成数据丢失)
- 少部分副本宕机
物理存储
Kafka 消息是以主题为单位进⾏归类,各个主题之间是彼此独⽴的,互不影响;每个主题⼜可以分为⼀个或多个分区;每个分区各⾃存在⼀个记录消息数据的⽇志⽂件。
图中,创建了⼀个 tp_demo_01 主题,其存在6个 Parition,对应的每个Parition下存在⼀个 [TopicParition] 命名的消息⽇志⽂件。在理想情况下,数据流量分摊到各个 Parition 中,实现了负载均衡的效果。在分区⽇志⽂件中,你会发现很多类型的⽂件,⽐如: .index、.timestamp、.log、.snapshot 等。
类别作⽤
后缀名 | 说明 |
---|---|
.index | 偏移量索引⽂件 |
.timestamp | 时间戳索引⽂件 |
.log | ⽇志⽂件 |
.deleted | 准备删除文件 |
.cleaned | ⽇志清理时临时⽂件 |
日志索引文件
配置条⽬ | 默认值 | 说明 |
---|---|---|
log.index.interval.bytes | 4096(4K) | 增加索引项字节间隔密度,会影响索引⽂件中的区间密度和查询效率 |
log.segment.bytes | 1073741824(1G) | ⽇志⽂件最⼤值 |
log.roll.ms | 当前⽇志分段中消息的最⼤时间戳与当前系统的时间戳的差值允许的最⼤范围,单位毫秒 | |
log.roll.hours | 168(7天) | 当前⽇志分段中消息的最⼤时间戳与当前系统的时间戳的差值允许的最⼤范围,单位⼩时 |
log.index.size.max.bytes | 10485760(10MB) | 触发偏移量索引⽂件或时间戳索引⽂件分段字节限额 |
切分文件
当满⾜如下⼏个条件中的其中之⼀,就会触发⽂件的切分:
- 当前⽇志分段⽂件的⼤⼩超过了 broker 端参数 log.segment.bytes 配置的值。 log.segment.bytes 参数的默认值为 1073741824,即 1GB。
- 当前⽇志分段中消息的最⼤时间戳与当前系统的时间戳的差值⼤于 log.roll.ms 或 log.roll.hours 参数配置的值。如果同时配置了 log.roll.ms 和 log.roll.hours 参数,那么 log.roll.ms 的优先级⾼。默认情况下,只配置了 log.roll.hours 参数,其值为168,即 7 天。
- 偏移量索引⽂件或时间戳索引⽂件的⼤⼩达到 broker 端参数 log.index.size.max.bytes 配置的值。 log.index.size.max.bytes 的默认值为 10485760,即 10MB。
- 追加的消息的偏移量与当前⽇志分段的偏移量之间的差值⼤于 Integer.MAX_VALUE ,即要追加的消息的偏移量不能转变为相对偏移量。
索引⽂件切分过程
索引⽂件会根据 log.index.size.max.bytes 值进⾏预先分配空间,即⽂件创建的时候就是最⼤值当真正的进⾏索引⽂件切分的时候,才会将其裁剪到实际数据⼤⼩的⽂件。这⼀点是跟⽇志⽂件有所区别的地⽅。其意义降低了代码逻辑的复杂性。
日志存储
一、 索引
偏移量索引⽂件⽤于记录消息偏移量与物理地址之间的映射关系。时间戳索引⽂件则根据时间戳查找对应的偏移量。
二、关于消息偏移量
消息存储
1. 消息内容保存在log⽇志⽂件中。
2. 消息封装为Record,追加到log⽇志⽂件末尾,采⽤的是顺序写模式。
3. ⼀个topic的不同分区,可认为是queue,顺序写⼊接收到的消息。
三、偏移量
1. 位置索引保存在index⽂件中
2. log⽇志默认每写⼊4K(log.index.interval.bytes设定的),会写⼊⼀条索引信息到index⽂件中,因此索引⽂件是稀疏索引,它不会为每条⽇志都建⽴索引信息。
3. log⽂件中的⽇志,是顺序写⼊的,由message+实际offset+position组成
4. 索引⽂件的数据结构则是由相对offset(4byte)+position(4byte)组成,由于保存的是相对第⼀个消息的相对offset,只需要4byte就可以了,可以节省空间,在实际查找后还需要计算回实际的offset,这对⽤户是透明的。
清理日志文件
Kafka 提供两种⽇志清理策略:
- ⽇志删除:按照⼀定的删除策略,将不满⾜条件的数据进⾏数据删除()
- 基于时间 :⽇志删除任务会根据 log.retention.hours/log.retention.minutes/log.retention.ms 设定⽇志保留的时间节点。如果超过该设定值,就需要进⾏删除。默认是 7 天, log.retention.ms 优先级最⾼。
- 基于⽇志⼤⼩:⽇志删除任务会检查当前⽇志的⼤⼩是否超过设定值。设定项为 log.retention.bytes ,单个⽇志分段的⼤⼩由 log.segment.bytes 进⾏设定。
- 删除过程:
- 从⽇志对象中所维护⽇志分段的跳跃表中移除待删除的⽇志分段,保证没有线程对这些⽇志分段进⾏读取操作。
- 这些⽇志分段所有⽂件添加 上 .delete 后缀。
- 交由⼀个以 “delete-file” 命名的延迟任务来删除这些 .delete 为后缀的⽂件。延迟执⾏时间可以通过file.delete.delay.ms 进⾏设置
- ⽇志压缩:针对每个消息的 Key 进⾏整合,对于有相同 Key 的不同 Value 值,只保留最后⼀个版本。
Kafka 提供 log.cleanup.policy 参数进⾏相应配置,默认值: delete ,还可以选择 compact 。
kafka零拷贝
读取⽂件,socket发送
1. 第⼀次:将磁盘⽂件,读取到操作系统内核缓冲区;
2. 第⼆次:将内核缓冲区的数据,copy到application应⽤程序的buffer;
3. 第三步:将application应⽤程序buffer中的数据,copy到socket⽹络发送缓冲区(属于操作系统内核的缓冲区);
4. 第四次:将socket buffer的数据,copy到⽹络协议栈,由⽹卡进⾏⽹络传输。
实际IO读写,需要进⾏IO中断,需要CPU响应中断(内核态到⽤户态转换),尽管引⼊DMA(Direct Memory Access,直接存储器访问)来接管CPU的中断请求,但四次copy是存在“不必要的拷⻉”的。实际上并不需要第⼆个和第三个数据副本。数据可以直接从读缓冲区传输到套接字缓冲区。
kafka的两个过程:
1、⽹络数据持久化到磁盘 (Producer 到 Broker)
2、磁盘⽂件通过⽹络发送(Broker 到 Consumer)
数据落盘通常都是⾮实时的,Kafka的数据并不是实时的写⼊硬盘,它充分利⽤了现代操作系统分⻚存储来利⽤内
存提⾼I/O效率。