kafka集群
Kafka的设计都是为了实现kafak消息队列消费数据的语义
Kafka消息队列中数据消费的三种语义:
- at-most-once:至多一次
会出现数据丢失的问题 - at-least-once:至少一次
会出现数据重复的问题 - exactly-once:有且仅有一次
只消费处理成功一次
所有消息队列的目标
kakfa卡只能保证局部有序,如何实现全局有序
kafka中每个分区内有offset保证局部有序
1.如果topic只有 一个分区可以实现全局有序
2.写数据的时候指定分区编号,只想一个分区内写数据
Kafka如何保证自身数据安全
多副本机制
生产者和消费者跟leader副本进行读写
follower副本跟leader副本同步数据
且读写的内容只能在hw高水位线之前
副本同步机制
为什么会存在同步?因为读写只跟leader副本打交道。所以follower副本需要同步数据。
副本集合概念:
AR 该分区的所有副本 ALL
-
ISR 所有副本中处在同步状态的 健康良好的 IN Sync
只有ISR中的副本才有选举资格 成为新的leader 优先顺序 从左往右谁来负责leader副本选举? kafka中主角色controller
谁来kafka主角色controller选举呢? 通过zk集群选举。【临时节点 监听 唯一性】 -
OSR 非同步状态的 Out Sync
正常情况下 OSR集合中应该是空的 每个副本都应该处于健康的状态
ISR同步超时 就会被打入OSRAR=ISR+OSR 名词: LSO start 每个副本的第一个消息offset 正常是0。如果开启了删除清理 之前的数据就会被删除 LEO end 每个副本的【下一个待写入】的offset值 HW 高水位线 只有它之前的消息才能被消费者拉取消费 hw=min(leo) 类似于木桶效应
Producer生产者如何保证生产的数据不丢失\不重复
-
如何保证kafka数据不丢失
ack校检和重试机制- ACK级别
0 不管ack 只发送数据
1 当leader副本保存成功 返回ack给生产者
-1|all 当所有的ISR副本都保存同步成功 返回ack 【最安全 最慢】 - 重试机制
因为网络质量等偶发因素导致的消息发送失败 可以通过重试机制
如果因为代码问题 集群环境问题 重试一百万次有没有意义
- ACK级别
-
如何保证数据写入kafka不重复?
为什么会重复?
1_重试机制,如果前一个ack还没有返回 生产者认为失败了基于重试机制重新发了一遍
2_ack结果延迟 丢失
实际上kafka已经存储成功了 只不过生产者没有正确准确的收到ack内部设计了什么机制?
1_幂等性机制 操作一次和操作多次 效果是一样的。 不跟次数有关。 重复支付 重复提交 常量函数。
2_给每个生产者发送的消息内部编号 自增id
kafka在保存数据的时候 就会判断编号 如果已经有了 不保存了直接返回ack
生产者写入数据分区规则
当Producer生产者向Topic队列中发送数据时,如何确定发送到哪个分区Partition呢?
1.如果用户指定了分区,就向指定分写入数据
-
2.如果用户不指定分区,看是否有自定义分区规则
- 2_1 如果没有指定自定义分区规则,按照默认的规则分区规则
如果有key,根据 key的hash值%分区个数
Utils.murmur2(keyBytes) % numPartitions
计算key哈希值,对partition分区个数进行取模操作 结果就是分区编号。
只要key一样,一定到同一个分区。如果没有key:
a_老版本 采用轮询策略
b_新版本 stickyPartition 黏性策略
if (keyBytes == null) {
return stickyPartitionCache.partition(topic, cluster);
}什么叫做黏性策略呢?
首先判断当前有没有partition的连接 如果连接有效 直接使用这个连接
Integer newPart = oldPart;
如果没有分区连接,那就随机选择一个分区创建连接 把数据都写入这个分区
random- 2_2 如果有自定义分区规则,按照自定义分区规则分配
- 2_1 如果没有指定自定义分区规则,按照默认的规则分区规则
Consumer消费者如何保证数据不重复不丢失
Kafka记录了每次消费者消费后的消费记录
当消费者来消费的时候,只需消费上一次offset+1的数据就可以了
-
消费者消费数据的三种方式
指定topic读取的是指定topic下所有分区的数据
指定topic partition读取的是指定topic下的某个分区的数据
指定topic partition offset读取的是指定topic下 指定分区的 某个offset开始
订阅主题 subscribe 消费该主题的所有分区数据
订阅主题指定分区 assign 消费指定主题分区的数据
精准消费 定位消费 seek 消费指定主题分区 指定偏移量消费
需要先订阅topic 和 分区,然后才能seek 偏移量
消费者开始消费数据时,从哪里开始消费
- step1: 第一次消费规则 由属性决定
lastest 从0开始
earliest 从最新的开始(默认)
//设置消费的位置 从哪里开始消费 合法参数:latest | earliest
props.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
step2:第二次及以后 从上一次消费完的offset +1位置开始消费
以上成为消费记录,那么kafka如何保存消费记录的呢?
消费者内存中 自己维护 程序不断 不重启的情况下 【优先使用】。
持久化存储在磁盘、存储介质: 【重启程序的依据】
存储kafka中(默认) 自创了主题_consumer_offsets
存储zk/mysql/redis
内存消费记录:kafka自己记录的非常标准,但是程序重启之后就没有了,需要从文件当中读
文件消费记录:kafka自己有一个topic 专门用来存储每个消费者消费的offset
offset偏移量管理
消费者消费完的记录需要提交,怎样提交?
- 自动提交
- 根据时间周期提交下一下的消费的offset,默认每五秒提交一次
-
风险:
数据丢失
数据重复
image.png
为了防止数据丢失,或者重复消费我们选择手动提交
- 手动提交offset
先消费数据,然后再提交offset
风险:如果此时我已经消费了两个分区的数据,第三个分区还没有消费完,程序崩溃了,offset没有提交,就会导致,下次程序启动的时候 重复消费数据 - 手动提交--基于分区提交offset
offsets.put(partition,new OffsetAndMetadata(consumerOffset+1));
把当前分区消费的最后一条日志的offset +1,提交上去。 -
手动提交分区offset探秘
image.png
消费者在自己的内存中维护了消费记录
当内存中有记录的时候,程序之间从内存当中读取消费记录,这个消费记录是自己维护的正确的
而磁盘中的消费记录,只有在程序重启 内存中的消费记录丢失了 才会根据磁盘去消费
消费者消费数据分配策略
问题:Kafka 消费组Consumer Group中多个消费者Consumer如何消费Topic队列中数据?
前提-kafka中同一个消费组中的消费者规则:
一个分区只能被一个消费者消费
一个消费者可以消费多个分区
另 一个分区的内容,可以被不同消费组内的消费者消费
最理想的状态:消费者和分区 一对一
策略:
范围分配
轮询分配
粘性分配
1_RangeAssignor 分配策略-范围分配
- Kafka中默认的分配规则
- 一个topic中所有的分区按照消费者的个数平均分,多的就分配给编号小的消费者
优点:适用于消费topic比较少的情况,分配会比较平均
ex: 一个topic内有七个分区 有三个消费者c1 c2 c3
c1 (0,1,2) c2(3,4) c3(56)
缺点: 不适应于多个消费者消费多个topic,会造成编号小的负载压力大的情况
列如:三个消费者 消费三个topic 每个topic有7个分区会导致
c1 -t1(0,1,2) t2(0,1,2) t3(0,1,2)
c2 -t1(3,4) t2(3,4) t3(3,4)
c3 t1(5,6) t2(5,6) t3(5,6)
2_RoundRobinAssignor 分配策略-轮询策略
- 给每个Topic和其分区编号,轮询分配给消费者
一个消费者分配一个 轮询分配
适合:所有消费者都订阅相同的主题。
缺点:如果有消费者故障 或者加入新的消费者 之前全推倒 重新分配
缺点: c1、c2消费第一个topic,c2、c3消费第二个Topic、c3消费第三个Topic,指定消费者消费Topic
会导致c3 (t2-p2,t3-p1-p2-p3)负载过大
3_StickyAssignor 分配策略-粘性策略(推荐)
粘性策略注意针对的是消费过程者,如果有消费者挂掉了.该如何分配其正在消费的分区.
不出故障的时候跟轮询一样,出故障之后正常的都不动,轮询的分配故障后的分区