1.使用场景
① 大数据领域
网站行为分析、日志聚合、应用监控、流式数据处理、在线和离线数据分析等领域。
② 数据集成
将消息导入MaxCompute、OSS、RDS 、Hadoop、 HBase等离线数据仓库。
③ 流计算集成
与StreamCompute、 E-MapReduce、 Spark、Storm等 流计算引擎集成。
2. 架构原理
① Broker
Kafka 的服务也叫做 Broker,默认是 9092 的端口。生产者和消费者都需要跟这个 Broker 建立一个连接,才可以实现消息的收发。
② 消息
客户端之间传输的数据叫做消息,或者叫做记录。消息在传输的过程中需要序列化
③ 生产者
为了提升消息发送速率,生产者不是逐条发送消息给 Broker,而是批量发送。多少条发送一次由一个参数决定。默认16K,默认发送等待时间5毫秒
pros.put(“batch.size”,16384);
pros.put(“linger.ms”, 5);
④ 消费者
消费者获取消息有两种模式,Pull 模式 和 Push 模式
Pull 模式就是消费放在 Broker,消费者自己决定什么时候去获取。Push 模式是消息放在 Consumer,只要有消息到达 Broker,都直接推给消费者。
KAFKA默认Pull 模式,max.poll.size=500 默认拉取500个消息
④ Topic
Topic,是一个逻辑概念,可以理解为一组消息的集合
生产者跟消费者通过TOPIC关联,生产者和 Topic 以及 Topic 和消费者的关系都是多对多。一个生产者可以发送消息到多个 Topic,一个消费者也可以从多个 Topic 获取消息(但是不建议这么做)。
生产者发送消息时,如果 Topic 不存在,会自动创建。由一个参数控制:auto.create.topics.enable,默认为 true。
⑤分区(Partition)
一个 Topic 中的消息太多,一是,不方便把数据分布在不同的机器上实现扩展,二是,所有的客户端操作的都是同一个 Topic,在高并发的场景下性能会大大下降
Kafka 引入了一个分区(Partition)的概念。一个 Topic 可以划分成多个分区。分区在创建 Topic 的时候指定,每个 Topic 至少有一个分区 。如果没有指定分区数,默认的分区数是一个:服务端由一个参数控制分区数:num.partitions=1
Partition 思想上有点类似于分库分表,实现的也是横向扩展 和 负载 的目的。
⑥ Partition 副本 Replica 机制
如果 Partition 的数据只存储一份,在发生网络或者硬件故障的时候,该分区的数据就无法访问或者无法恢复了。
每个 Partition 可以有若干个副本(Replica),副本必须在不同的 Broker 上面,一般我们说的副本包括其中的主节点,服务端有一个参数控制默认的副本数:offsets.topic.replication.factor。副本的个数不大于节点数
Partition 副本有 Leader和 Follower 的概念,Leader通过zookeeper 选举产生。生产者发消息、消费者读消息都是针对Leader
⑦ Segment
如果一个 Partition 只有一个 log 文件,消息不断地追加,这个 log 文件也会变得越来越大,这个时候要检索数据效率就变低了
Partition 再进一步切分,切分出来的单位就叫做段(Segment)。实际上 Kafka 的存储文件是划分成段来存储的
每个 Segment 都有至少有 1 个数据文件和 2 个索引文件,这 3 个文件成套出现
一个Segment默认1073741824 bytes(1G),由参数控制:log.segment.bytes
⑧ Consumer Group消费者组
生产者生产消息的速率过快,会造成消息在 Broker堆积,通过增加消费者的数量,可以提升消费者的消费速度
消费同一个TOPIC的消费者划分到同一个消费者组,每一个消费者组通过groupid来标识。
消费同一个 Topic 的消费者不一定是同一个组,只有 group id 相同的消费者才是同一个消费者组。
同一个 Group 中的消费者,不能消费相同的 Partition,Partition 要在消费者之间分配。
⑨ Consumer Offset
Partition 里面的消息顺序写入,被读取之后不会被删除。
通过对消息进行编号,用来标识一条唯一的消息,可以保证消费者接着上次的位置读取消息,或者从某个特定的位置读取消息,进而不会出现重复消费的情况
offset之前保存在zookeeper,现在在KAFKA内部保存
2. 生产者原理
2.1 消息幂等性
消息幂等性用于KAFKA消费端,解决消息重复消费的情况。
Kafka 在 Broker 实现了消息的重复性判断,首先依赖于 “生产者的消息的唯一的标识”, 由参数控制:
props.put(“enable.idempotence”, true);
设置成 true 后,Producer 自动升级成幂等性 Producer。
幂等性的生产者每个客户端都有一个唯一的编号 PID(Producer ID,且发送的每条消息都会带相应的 sequence number,
KAFKA Server 端就是根据这个值来判断数据是否重复。如果说发现 sequence number 比服务端已经记录的值要小,就是出现消息重复了
sequence number 并不全局有序,不能保证所有时间上的幂等。只能保证某个主题的一个分区上不出现重复消息;
Producer 进程一次运行,当重启了之后,幂等性不保证。
如果要实现多个分区的消息原子性,需要用到 Kafka 的事务机制
2.2 生产者事务
两阶段提交(2PC)
有一个协调者的角色,叫做 Transaction Coordinator。
事务管理必须要有事务日志,来记录事务的状态,以便 Coordinator 在意外挂掉之后继续处理原来的事务。跟消费者 Offset 的存储一样,Kafka 使用 topic_transaction_state 来记录事务状态。
要有事务Id,transaction.id,配置了 transaction.id,则此时 enable.idempotence 会被设置为 true(事务实现的前提是幂等性)。事务 ID 相同的生产者,可以接着处理原来的事务。
2.3 生产者消费发送流程
① main线程启动,初始化KafkaProducer的时候,创建Sender线程,Sender线程负责与Broker通信
② 拦截器的作用是实现消息的定制化
③ 利用指定的工具对 key 和 value 进行序列化,除了自带的序列化工具之外,可以使用如 Avro、JSON、Thrift、Protobuf 等,
或者使用自定义类型的序列化器来实现,实现 Serializer 接口即可
④ 分区器为一条消息指定路由到的分区
⑤ 选择分区以后并没有直接发送消息,而是把消息放入了消息累加器
2.4. 数据可靠性保证
2.4.1 服务端响应策略
在KAFKA中,单个 Partition(Leader)写入成功,如果有多个副本,所有的 Follower 全部完成同步之后,服务端再发送 ACK 给客户端
服务端确认以后,生产者才发送下一轮的消息,否则重新发送数据。
这种响应策略延迟相对来说高一些,但是节点挂掉的影响相对来说小一些,因为所有的节点数据都是完整的。
假设有一个 Follower出了问题,没有办法从 Leader 同步数据。按照这个规则,Leader 就要一致等待,无法发送 ACK
2.4.2 ISR
我们可以把那些 正常和 leader 保持同步的 replica 维护起来,放到一个动态 set 里面,这个就叫做 in-sync replica set(ISR),只要 ISR 里面的 follower 同步完数据之后,KAFKA服务端就给客户端发送 ACK。
多久没有向 Leader 同步数据,被踢出 ISR,由参数:replica.lag.time.max.ms 决定(默认值 30 秒)
如果 leader 挂了,就会从 ISR 重新选举 leader。
2.4.3 ACK应答机制
适合不是同步全部数据,允许有些数据丢失,不管数据落盘没落盘的情况
三种可靠性级别:
① pros.put(“acks”,“0”);
producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据;
② pros.put(“acks”,“1”);
producer 等待 broker 的 ack,partition 的 leader 落盘成功,后返回 ack,如果在 follower 同步成功之前 leader 故障,那么将会丢失数据;
③ pros.put(“acks”," -1");
producer 等待 broker 的 ack,partition 的 leader 和 follower
全部落盘成功后才返回 ack
如果在 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,没有给生产者发送 ACK,那么会造成数据重复。在这种情况下, 把 reties 设置成 0(不重发),才不会重复。
由服务端参数控制: 异常日动重试次数:pros. put(“retries”,0);
三种机制,性能依次递减 (producer 吞吐量降低),数据健壮性则依次递增
3. 消息保留机制
Kafka 的日志(数据)在消费以后不删除,所以可以顺序追
加写入,但总会有磁盘撑爆的情况,所以对于比较老旧的日志,需要有一定清理策略。
日志的清理开关默认是开启的:
log.cleaner.enable=true
但有两种清理方式:log.cleanup.policy=delete | Compact
3.1 直接删除(delete)
直接删除delete 方式:
log.retention.check.interval.ms=300000默认每5分钟,由定时任务清理一次
3.1.1 时间粒度删除
默认删除超过168个小时的数据,也即一周的数据
log.retention.hours
也有更细粒度的配置:分钟和毫秒,默认值为空
log.retention.minutes
log.retention.ms
选用的时间单位越小,优先级越高
3.1.2 日志大小粒度删除
假设 Kafka 产生消息的速度是不均匀的,有的时候一周几百万条,有的时候一周几千条,那这个时候按照时间删除就不合理,所以KAFKA提供了第二种删除策略就是根据日志文件总大小删除,先删旧的消息,删到不超过这个大小为止
log.retention.bytes
默认值是-1,代表不限制大小,想写多少就写多少。
也可以对单个 Segment 文件大小进行限制
log.segment.bytes
默认值 1073741824 字节(1G)
3.2 日志压缩(Compact)
压缩就是把相同的 Key 合并为最后一个 Value。
topic:_consumer_offsets,存储的是消费者id 和 Partition 的 Offset 关系,消费者不断地消费消息 commit 的时候,不断顺序写入新的 Offset
4.高可用架构
4.1 Controller控制器
Kafka 先从所有 Broker 中选出唯一的一个Broker,用于控制Leader选举,这个Broker叫做控制器
其选举办法是,Broker在 ZK 里,竞争写入一个/controller 临时节点,哪个写入成功,哪个成为Controller
Controller的职责:
① 监听 Broker 变化。
② 监听 Topic 变化。
③ 监听 Partition 变化。
④ 获取和管理 Broker、Topic、Partition 的信息。
⑤ 管理 Partiontion 的主从信息
4.2 分区副本 Leader 选举
① AR
Assigned-Replicas
一个分区所有的副本
② In-Sync Replicas(ISR)
AR 中 跟 Leader 数据保持一定程度同步的副本
③ OSR
Out-Sync-Replicas
AR中跟 Leader 同步滞后过多的副本
AR=ISR+OSR。正常情况下 OSR 是空的,副本都正常同步,AR=ISR。
ISR是一个动态列表,副本同步延迟超过 30 秒,就踢出 ISR,进入 OSR,如果赶上来了,就加入 ISR
默认情况下,当 Leader 副本发生故障时,只有在 ISR 集合中的副本才有资格被选举为新的 Leader。
ISR 为空的情况下,也可以允许ISR之外的副本参与选举,由参数:unclean.leader.election.enable=false 控制,但不建议开启
Kafka 的选举实现,采用的类似于微软的 PacificA 算法。在这种算法中,默认是让 ISR 中第一个 Replica 变成 Leader。比如 ISR 是 1、5、9,优先让 1 成为 Leader。
4.3 主从副本同步
Leader 确定之后,客户端的读写只能操作 Leader 节点。Follower 需要向 Leader同步数据。
4.3.1 LEO & HW
不同副本同步时的OFFSET不一样,为此提出了两个概念:
① LEO
Log End Offset
下一条等待写入的消息的 Offset(最新的 Offset + 1),
② HW
Hign Watermark,ISR 中最小的 LEO。Leader 会管理所有 ISR 中最小的 LEO 作为 HW
Consumer 最多只能消费到 HW 之前的位置,也就是说:其他的副本没有同步过去的消息,是不能被消费的。这是因为如果在同步成功之前就被消费了,Consumer Group 的Offset 会偏大。如果 Leader 崩溃,中间会缺失消息。
4.3.2 同步过程
① Follower 节点向 Leader 发送一个 fetch 请求,Leader 向 Follower 发送数据后,也需要更新 Follower 的 LEO
② Follower 接收到数据响应后,依次写入消息并且更新 LEO
③ Leader 更新 HW(ISR 最小的 LEO)
4.3.3 Replica 故障处理
① Follower 故障
首先会被踢出 ISR,故障排除副本恢复以后,把高于 HW 的消息截掉。然后向 Leader 同步消息。追上 leader 之后(30 秒),重新加入ISR。
② Leader 故障
首先从ISR中选举出Leader,然后其它Leader把高于 HW 的消息截取掉,然后从Leader同步数据。这种机制只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复(即不能保证可靠性)。
5. 消费者原理
5.1 消费者组和OFFSET关系
ex:消费者组gp-assign-group-1 和
一个TOPIC (assign) 的
五个PARTITION的偏移量关系,查看命令:
./kafka-consumer-groups.sh --bootstrap-server IP1:9092,IP2:9092,IP3:9092
–describe --group gp-assign-group-1
主要描述了消费者组中的一个consumer 和 Topic中的一个 Partition 的消费位置,即OFFSET(Offset 在 Partition 中连续编号而不是全局连续编号)。
5.2 OFFSET存储位置
消费者组和OFFSET对应关系,保存在一个特殊的TOPIC中:__consumer_offsets,默认有 50 个分区(offsets.topic.num.partitions 默认是 50)
① 查看主题__consumer_offsets的数据分布情况
./kafka-topics.sh
–topic __consumer_offsets
–describe
–bootstrap-server IP1:9092
② 一个 consumer group 的 Offset 放在Topic 的哪个分区
Math.abs(“消费者组名”.hashCode()) % 50)
③ 查看 __consumer_offsets的内部结构
./kafka-console-consumer.sh
–topic __consumer_offsets
–bootstrap-server IP1:9092,IP2:9092,IP3:9092
–formatter
“kafka.coordinator.group.GroupMetadataManager$OffsetsMessageFormatter”
–from-beginning
这个 Topic里面主要存储两种对象:
GroupMetadata:保存了消费者组中各个消费者的信息(每个消费者有编号。[消费者组,主题,分区]
OffsetAndMetadata:保存了消费者组和各个 partition 的 offset 位移信息元数据
④ 消费者找不到OFFSET
增加了一个新的消费者组去消费一个 Topic 的某个 Partion,这种情况会出现消费者找不到OFFSET
可以控制消费者的消费位置,由参数控制:auto.offset.reset
默认值是 latest,也就是从最新的消息(最后发送的)开始消费的。历史消费是不能消费的。
earliest 代表从最早的(最先发送的)消息开始消费。可以消费到历史消息。
5.3 OFFSET的更新
消费者组的 Offset 保存在 Broker,但是由消费者上报给Broker。消费者消费了消息要有一个COMMIT的操作,便于Broker更新OFFSET
消费者可以自动提交,或手工提交,
由参数:enable.auto.commit 控制
默认是 true。代表消费者消费消息以后自动提交此时 Broker 会更新消费者组的 Offset。auto.commit.interval.ms 可以控制提交频率,默认是5秒钟
enable.auto.commit=false,代表手工提交,也分两种方式:
consumer.commitSync() 手动同步提交
consumer.commitAsync() 手动异步提交
如果不提交或者提交失败,Broker 的 Offset 不会更新,消费者组下次消费的时候会消费到重复的消息。
5.4 消费者消费策略
① RangeAssignor
根据分区编号,按照范围将分区中的日志分配给消费者,这也是默认消费策略
② RoundRobinAssignor(轮询)
③ StickyAssignor(粘滞)
这种策略复杂一点,但是相对来说均匀一点(每次的结果都可能不一样)。原则:
1)分区的分配尽可能的均匀
2)分区的分配尽可能和上次分配保持相同