简介:
Kafka是一个分布式系统:它以集群的方式运行,可以灵活伸缩(分区),在内部通过 复制数据提升容错能力和高可用性 , 高可用(主从集群),高吞吐(分区分段+索引,顺序读写,0拷贝,批量写),分布式消息持久化(文件系统),集群部署的消息中间件。
0.9版本之前offset 存储在zk,0.9版本之后offset存储在本地
注册中心 zookeeper集群
1.注册中心,producer,broker,consumer 注册 和 心跳 ,
2.topic 和 partition 等 meta 数据;
3.还负责 broker 故障发现,partition leader 选举,负载均衡等功能
leader容灾
leader的节点监听:controller会在Zookeeper的/brokers/ids节点上注册Watch,一旦有broker宕机,它就能知道。
读取对应partition的ISR(in-sync replica已同步的副本)列表,选一个出来做leader。
Kafka的Leader选举是通过在ZooKeeper上创建/controller临时节点来实现leader选举,并在该节点中写入当前broker的信息 {“version”:1,”brokerid”:1,”timestamp”:”1512018424988”}
利用ZooKeeper的强一致性特性,一个节点只能被一个客户端创建成功,创建成功的broker即为leader,即先到先得原则,leader也就是集群中的controller,负责集群中所有大小事务。当leader和ZooKeeper失去连接时,临时节点会删除,而其他broker会监听该节点的变化,当节点删除时,其他broker会收到事件通知,重新发起leader选举。
producer 集群
发送消息方式:
1.直接发送消息 acks = 0
2.异步发送消息 acks = 1 这意味着producer在ISR中的leader已成功收到数据并得到确认。如果leader宕机了,则会丢失数据。
3.同步发送消息 acks = all (-1)
配合min.insync.replicas使用,控制写入isr中的多少副本才算成功,producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。但是这样也不能保证数据不丢失,比如当ISR中只有leader时
发送机制
0.有key hash(key)% partitionSize, 没有就轮询
1.batch.size默认值是16KB,凑够16KB的数据才会发送
2.linger.ms batch最大的空闲时间
但是消息并不是必须要达到一个batch尺寸才会批量发送到服务端呢,Producer端提供了另一个重要参数linger.ms,用来控制batch最大的空闲时间,超过该时间的batch也会被发送到broker端。
3.max.in.flight.requests.per.connection"这个参数设置的值(默认是5) 消息顺序性
就是这5条消息其中的一条发送失败了,如果进行重试,那么重发的消息其实是在下个批次的,这就会造成消息顺序的错乱
发送多少条消息后,接收服务端确认,比如设置为1,就是每发一条就要确认一条,设置为5就是,发送5条消息等待一次确认
4.buffer.memory 消息缓冲池默认值32MB
sender 来不及发送,并发太快,线程阻塞 max.block.ms默认值30000ms(30秒),生产者会抛出超时异常。线程无法释放,很快tomcat就会没有线程可用
Sender线程来不及把消息发送到Kafka,当生产者的发送缓冲区已满(producer 发送消息太快),这时候就会线程阻塞。阻塞时间达到 max.block.ms默认值30000ms(30秒) 时,生产者会抛出超时异常。线程分分钟就会全部被阻塞,web容器在没有可用线程时收到的请求一般还会存放在队列中等待响应,线程得不到释放意味着内存同样无法被释放,所以很快内存就溢出了。
5.批量发送机制
Accumulator 负责 消息按照每个分区进行分批, sender线程会去遍历 分区的每个批次的消息 去发送
Kafka中producer的幂等是怎么实现的
kafka 会维护一个 producer 的pid 对应 partition的分区 的一个序列号,这个序列号自增
小于 就丢弃,大于直接报错(说明消息丢失)
为了实现生产者的幂等性,broker 端会在内存中为每一对 <PID,分区> 维护一个序列号,对于收到的每一条消息,只有当它的序列号的值(SN_new)比 broker 端中维护的对应的序列号的值(SN_old)大1(即 SN_new = SN_old + 1)时,broker 才会接收它。如果 SN_new< SN_old + 1,那么说明消息被重复写入,broker 可以直接将其丢弃。如果 SN_new> SN_old + 1,那么说明中间有数据尚未写入,出现了乱序,暗示可能有消息丢失,对应的生产者会抛出 OutOfOrderSequenceException,这个异常是一个严重的异常,后续的诸如 send()、beginTransaction()、commitTransaction() 等方法的调用都会抛出 IllegalStateException 的异常。
producer 性能优化的设计
1.增加batch.size
2.linger.ms(消息滞留时间),启用压缩 LZ4,关闭重试
3.buffer.memory 缓存池 避免内存溢出 发送消息线程阻塞
4.max.in.flight.requests.per.connection"这个参数设置的值(默认是5) 消息顺序性
5.producer 可以通过随机或者 hash 等方式,将消息平均发送到多个 partition 上,以实现负载均衡。
6.Accumulator 组件负责分区进行分批, sender线程负责批量发送, 减少网络请求次数, broker 也会减少磁盘IO
Broker
消息以partition的形式存放在 broker中 , 分区副本 不能和 master partition 放在图片同一个broker
主从同步机制(ISR机制 写入机制??)
follower如果超过10秒没有到leader这里同步数据,就会被踢出ISR
AR: Assigned Replicas的缩写,是每个partition下所有副本(replicas)的统称;
ISR: In-Sync Replicas的缩写,是指副本同步队列,ISR是AR中的一个子集;
LEO:LogEndOffset的缩写,表示每个partition的log最后一条Message的位置。
HW: HighWatermark的缩写,是指consumer能够看到的此partition的位置。 取一个partition对应的ISR中最小的LEO作为HW,consumer最多只能消费到HW所在的位置。
1.leader 和 follower 同步 offset 2. follower 更新本地offset 3. 更新后的 offset 发送给 leader 更新 leo,根据leo 更新 HW 4. 同步 hw (leader 和 follower) -------- 加入 ISR列表 (数据同步完成 节点列表)
1.Follower发送FETCH请求给Leader (rplica offset)。
2.replica 的offset < leader offset,拉取数据 更新 leader remote offset
3.replica 收到消息 更新本地 offset
4.replica 发送 offset 新的值过去,leader 发现 remote 的 leo = offset,就更新 remote 的 hw 和 自己的 hw
5.leader 发送 hw 给 replica, replica 收到消息 更新本地 hw
6.replica 加入 ISR
Leader和Follower的HW值更新时机是不同的,Follower的HW更新永远落后于Leader的HW。这种时间上的错配是造成各种不一致的原因。
注意:::!!因此,对于消费者而言,消费到的消息永远是所有副本中最小的那个HW。
分区分配机制
不是mod 求余数:
因为这样前面的broker的partition 会多于 后面的
kafka是先随机挑选一个broker放置分区0,然后再按顺序放置其他分区。
刷盘机制
当达到下面的消息数量时,会将数据flush到日志文件中。默认10000
log.flush.interval.messages=10000
当达到下面的时间(ms)时,执行一次强制的flush操作。interval.ms和interval.messages无论哪个达到,都会flush。默认3000ms
log.flush.interval.ms=1000
检查是否需要将日志flush的时间间隔
log.flush.scheduler.interval.ms = 3000
持久化机制 分段文件 segment 顺序写 + index 稀疏索引 + 二分法 + 顺序查找
总结:partitoin ---- 多个segment 顺序写 -------- 以最小offset命名 ---- 根据二分法查找 segment文件
index 稀疏索引 --- 文件名 和 segment 一样 --- 二分法查找 index --- offset 找到 对于 position --- segment 中的 message
因为是稀疏索引,所以有些是没有记录,会找到小的position 再顺序扫描
每个partition对应了操作系统上的一个 文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系 统分区分桶的设计思想。
partition 物理上由多个 segment 文件组成,每个 segment 大小相等,顺序读写。每个 segment数据文件以该段中最小的 offset 命名,文件扩展名为.log。这样在查找指定 offset 的 Message 的时候,用二分查找就可以定位到该 Message 在哪个 segment 数据文件中
index 文件中并没有为数据文件中的每条 Message 建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。
时间戳索引文件则根据指定的时间戳(timestamp)来查找对应的偏移量信息。
日志清理
broker 端参数 log.cleanup.policy 来设置日志清理策略:1.基于时间 2.基于日志大小 3.基于日志起始偏移量
过期时间默认为:log.retention.hours=168
Broker 设计机制
1.分区的负载均衡
2.持久化机制
3.日志清理机制
consumer
消费者的分区分配机制
总结:1.range (默认) 分区数量 除以 消费者线程 决定,除不尽的就会轮询消费者列表 分配
2.根据分区 hash 排序 去轮询分配
3.尽可能和上次一样
在 Kafka内部存在三种默认的分区分配策略:Range、RoundRobin和Sticky。
Range是默认策略。
用Partitions分区的个数除以消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。
RoundRobin策略
然后对TopicAndPartition列表按照hashCode进行排序,最后按照轮询的方式发给每一个消费线程。
StickyAssignor策略 这个单词可以翻译为“粘性的
分区的分配要尽可能的均匀;分区的分配尽可能的与上次分配的保持相同。
拉取消息机制
max.poll.records(每次拉取消息的数量)
总结: poll ---- fetch.min.bytes(最少数据量才会返回) ---- fetch.max.wait.ms(最大等待时间 数据不够也返回) 所以最少数据量 和 最大等待时间 会造成消息不实时 -------- max.partition.fetch.bytes(默认1M) 限制拉去消息 最大数据 -------- 要比 broker (message.max.bytes)发送的最大消息数据大, 不然 consumer 接收不了,
consumer max.partition.fetch.bytes(默认1M) 》 broker message.max.bytes
如果单词poll数据太多,消费者处理可能无法及时进行下一个轮询来避免会话过期。
fetch.min.bytes: 指定消费者从服务器获取记录的最小字节数。broker在收到消费者的数据请求时,如果可用的数据量小于配置指定的大小,会等有足够的数据再一起返回给消费者,以此降低消费者和broker的工作负载。fetch.max.wait.ms: 指定broker在没有收到足够数据时的最大等待时间,默认500ms,如果没有足够的数据流入broker,即使消费者尝试获取数据,broker也不会立即返回,而会等待离上次拉取数据时间间隔fetch.max.wait.ms才会返回给客户端。这个配置设置过大,会导致数据消费延迟,但可以降低消费者和broker的工作负载
max.partition.fetch.bytes:指定服务器从每个分区里返回给消费者的最大字节数。默认为1MB。这个配置值必须比broker能够接受的最大消息的字节数(message.max.bytes配置)大,否则消费者可能无法读取过大的消息,导致消费者一直刮起重试。另外还需要考虑消费者处理的时间,如果单词poll数据太多,消费者处理可能无法及时进行下一个轮询来避免会话过期。
避免消费者假死
总结:max.poll.interval.ms 默认间隔时间为300s 活跃检测机制 拉取消息的间隔 判断是否 consumer存活
岀现“活锁”的情况,是它持续的发送心跳,但是没有处理。为了预防消费者在这种 情况下一直持有分区,我们使用max.poll.interval.ms活跃检测机制。在此基础 上,如果你调用的poll的频率大于最大间隔,则客户端将主动地离开组,
以便其 他消费者接管该分区。发生这种情况时,你会看到offset提交失败。这是一种安 全机制,保障只有活动成员能够提交offsets所以要留在组中,你必须持续调用 polL
consumer 实例数设置
Kafka consumer 集成spring 怎么设置consumer 数量
container.setConcurrency(3)表示创建三个KafkaMessageListenerContainer实例。
如果在分布式情况下, 那么总的KafkaMessageListenerContainer实例数= 服务器机器数量*concurrency ;
https://blog.youkuaiyun.com/u010634066/article/details/109778491
consumer 设计机制
1.消息拉去机制
2.假死机制
3.消费实例+线程数
性能优化机制
1.批量读写 producer curator + sender broker 多segment 顺序写 + 最小offset命名 + index (减少内存占用)offset + position + 稀疏索引 + 二分法 + 顺序查找
0.kafka 架构
1.性能高吞吐量高 :消息缓冲机制 批量发送,并行读写,提高效率,因为多broker多partion
2.消息顺序一致性:指定key,指定消费线程消费的队列
3.消息的不丢失不重复:关闭自动提交offset、改为手动提交,消费后回调函数,消费判重,增加id
4.segment分段数据: 快速删除旧数据,快速定位到数据(按一定规则命名分段的数据文件名称并且每个数据文件数据量也变小)
5.kafka零拷贝 : 传统:1.磁盘到内核2.内核到应用3.应用到socket缓冲区,4.socket到网卡发送出去
kafka:1.磁盘到内核2.内核到网卡发送出去
6.读写快:采用顺序读写,硬盘顺序读写快,磁盘顺序读写速度超过内存随机读写。 弊端:无法删除数据,删除数据的机制:按时间和partition文件大小
partition数量对吞吐量的影响
好处
1.磁盘容量大,保存更多数据
2.更多的broker存放不同的partition,横向扩展提高了并发吞吐量
坏处
1.每个partition都需要开启文件句柄,占用服务器资源
2.占用更多的内存空(索引文件)
3.更多的磁盘被占用,partition多,副本也成本增加
4.副本增加,导致主从同步会增多,影响性能和吞吐量
需要调优测试,找到一个合适的配置
Kafka消息数据积压,Kafka消费能力不足怎么处理?
1)如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。
数据不丢失
broker
1.设置partition副本数量 replication.factor
producer
1.开启重试 retries , max.in.flight.requests.per.connections=1(开启重试如果需要保持顺序性,需要用这个参数指定生产者发多少条数据要接收一次确认信息。它的值越高,就会占用越多的内存,不过也会提升吞吐量。把它设为1 可以保证消息是按照发送的顺序写入服务器的,即使发生了重试)
2.开启手动ack
3.Producer要使用带有回调通知的API,也就是说不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。
Consumer
1.开启手动提交,消费者处理完消息之后再提交offset
解决方案
不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。
设置 acks = all。
设置 retries 为一个较大的值。
设置 unclean.leader.election.enable = false。
设置 replication.factor >= 3。
设置 min.insync.replicas > 1。 ISR中的最小副本数是多少,默认值为1
确保 replication.factor > min.insync.replicas。
确保消息消费完成再提交。
总结一下,Kafka 是能做到不丢失消息的,只不过这些消息必须是已提交的消息,而且还要满足一定的条件。
max_in_flight_requests_per_connection (int)
发送多少条消息后,接收服务端确认,比如设置为1,就是每发一条就要确认一条,设置为5就是,发送5条消息等待一次确认 ,如果大于1,比如5,这种情况下是会有一个顺序问题的,就是这5条消息其中的一条发送失败了,如果进行重试,那么重发的消息其实是在下个批次的,这就会造成消息顺序的错乱,所以如果需要保证消息的顺序,建议设置此参数为1 Default: 5.
Kafka中消息顺序消费 只可以保证同一个partition中的顺序是有序的
producer
1.指定key 或者 指定 partition
2.开启了retries ,需要配置max.in.flight.requests.per.connections=1
https://www.cnblogs.com/yfb918/p/12192857.html,https://blog.youkuaiyun.com/jacksonking/article/details/107458383
Kafka分布式的单位是partition分区,同一个partition用一个write ahead log组织,所以可以保证FIFO的顺序。不同partition之间不能保证顺序。但是绝大多数 用户都可以通过message key来定义,因为同一个key的message可以保证只发送 到同一个 partition
Kafka中发送1条消息的时候,可以指定(topic,partition, key)3个参数。partiton和key是可选的。如果指定了 partition,那就是所有消息发往同1个partition,就是有序的。并且在消费端,Kafka保证,1个partition只能被1个 consumer消费。或者你指定key(比如order id),具有同1个key的所有消息,会发往同1个partition
开启重试 retries , max.in.flight.requests.per.connections=1(开启重试如果需要保持顺序性,需要用这个参数指定生产者发多少条数据要接收一次确认信息。它的值越高,就会占用越多的内存,不过也会提升吞吐量。把它设为1 可以保证消息是按照发送的顺序写入服务器的,即使发生了重试)
kafka 消息重复原因和 解决重复消费
enable.auto.commit 默认值true,表示消费者会周期性自动提交消费的offset
auto.commit.interval.ms 在enable.auto.commit 为true的情况下, 自动提交的间隔,默认值5000ms 提交offset
max.poll.records 单次消费者拉取的最大数据条数,默认值
500 max.poll.interval.ms 默认值5分钟,表示若5分钟之内消费者没有消费完上一次poll的消息,那么consumer会主动发起离开group的请求(poll时才会提交offset)
原因:
1.设置自动提交offset,offset没提交 消费者宕机或者重启
2.消费数据超时,超过了 max.poll.interval.ms 设置的时间 ,会触发 Rebalance
3.消息重试的配置(retries),那就是当Broker给Producer返回acks确认时,网络出异常了,导致Producer没有收到ack确认,于是,Producer进行重试。Kafka在0.11版本之后,就为我们提供了定义幂等Producer的能力,可以通过将enable.idempotence.config参数设置为true来定义幂等Producer。
解决方案:
做好幂等。数据库方面可以(唯一键和主键)避免重复。在业务上做控制。
下游系统保证幂等性,重复消费也不会导致多条记录。
手动提交offset: 把commit offset和业务处理绑定成一个事务。
可以通过将enable.idempotence.config参数设置为true来定义幂等Producer。
kafka事务消息
2pc
Kafka引入了事务( Transactional )机制和 broker 中的一个新模块 Transaction Coordinator (TM角色 并且会生成一个 全局事务id)
producer 的send 操作结果 都要反馈给 TM 由TM 控制全局事务的成功和失败
kafka和ES的数据如何同步
1.kafka connector 可以用kafka 的connect,连接elasticsearch的话有开源的连接器。官方的
2.spark stream同步 也可以使用flume
可以使用流式处理组件spark streaming对kafka中的数据进行消费写到es中,也可以使用flume。 太庞大 比较重 我们只用其中一个功能
3.logstash 不稳定
KAFKA时间轮模式
Kafka中的时间轮(TimingWheel)是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(TimerTaskList)。TimerTaskList是一个环形的双向链表,链表中的每一项表示的都是定时任务项(TimerTaskEntry),其中封装了真正的定时任务TimerTask。
Kafka 网络线程模型
SocketServer:SocketServer是接收客户端Socket请求连接、处理请求并返回处理结果的核心类
Acceptor:Acceptor的主要任务是监听并且接收客户端的请求,同时建立数据传输通道—SocketChannel,然后以轮询的方式交给一个后端的Processor线程处理
Processor:从客户端的请求中读取数据和将KafkaRequestHandler处理完响应结果返回给客户端
RequestChannel: 请求队列 和 响应队列,responseListeners唤醒对应的Processor线程,最后Processor线程从响应队列中取出后发送至客户端。
KafkaRequestHandler:io处理线程池
KafkaApis:相应数据转发到队列中, 用于处理对通信网络传输过来的业务消息请求的中心转发组件
谈谈Kafka吞吐量为何如此高
多分区、batch sends kafka Reator 网络模型、pagecaches sendfile 零拷贝、数据 、压缩
1〉顺序读写
Kafka是将消息记录持久化到本地磁盘中的,有的人会认为磁盘读写性能差,可能 会对Kafka性能如何保证提岀质疑。实际上不管是内存还是磁盘,快或慢关键在于 寻址的方式,磁盘分为顺序读写与随机读写,内存也一样分为顺序读写与随机读 写。基于磁盘的随机读写确实很慢,但磁盘的顺序读写性能却很高,一般而言要高 岀磁盘随机读写三个数量级,一些情况下磁盘顺序读写性能甚至要高于内存随机读 写。
磁盘的顺序读写是磁盘使用模式中最有规律的,并且操作系统也对这种模式做了大 量优化,Kafka就是使用了磁盘顺序读写来提升的性能。Kafka的message是不断 追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了 显著提升。
上图就展示了 Kafka是如何写入数据的,每一个Partition其实都是一个文件,收 到消息后Kafka会把数据插入到文件末尾(虚框部分)。
这种方法有一个缺陷一一没有办法删除数据,所以Kafka是不会删除数据的, 它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个 offset用来表示读取到了第几条数据。
2〉零拷贝 pagecaches sendfile
零拷贝就是一种避免CPU将数据从一块存储拷贝到另外一块存储的技术。
传统:
1、第一次:将磁盘文件,读取到操作系统内核缓冲区;
2、第二次:将内核缓冲区的数据,copy到application应用程序的buffer;
3、第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
4、第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。
这种场景:是指读取磁盘文件后,不需要做其他处理,直接用网络发送出去。试想,如果读取磁盘的数据需要用程序(kafka服务端节点需要在对磁盘中的数据进行处理后再发送)进一步处理的话,必须要经过第二次和第三次数据copy,让应用程序在内存缓冲区处理。
注意: 内核缓存 的数据 到 socket缓冲区 Linux2.4做了优化 ,内核缓存不需要复制到 Socket 缓冲区,而只是将数据的位置和长度信息存储到 Socket 缓冲区 而不是复制
通过这种“零拷贝”的机制,Page Cache结合sendfile方法,Kafka消费端的性 能也大幅提升。这也是为什么有时候消费端在不断消费数据时,我们并没有看到磁 盘io比较高,此刻正是操作系统缓存在提供数据。
3〉分区分段+索引
Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的 partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个 文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系 统分区分桶的设计思想。
通过这种分区分段的设计,Kafka的message消息实际上是分布式存储在一 个一个小的segment中的,每次文件操作也是直接操作的segment。为了进一步 的查询优化,Kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上 的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也 提高了数据操作的并行度。
4〉批量读写
Kafka数据读写也是批量的而不是单条的。
在向Kafka写入数据时,可以启用批次写入,这样可以避免在网络上频繁传输 单个消息带来的延迟和带宽开销。假设网络带宽为1OMB/S,—次性传输1OMB的 消息比传输1KB的消息1OOOO万次显然要快得多
是提高消息吞吐量重要的方式,Producer 端可以在内存中合并多条消息后,以一次请求的方式发送了批量的消息给 broker,从而大大减少 broker 存储消息的 IO 操作次数。但也一定程度上影响了消息的实时性,相当于以时延代价,换取更好的吞吐量
4〉批量压缩
Producer 端可以通过 GZIP 或 Snappy 格式对消息集合进行压缩。Producer 端进行压缩之后,在Consumer 端需进行解压。压缩的好处就是减少传输的数据量,减轻对网络传输的压力,在对大数据处理上,瓶颈往往体现在网络上而不是 CPU(压缩和解压会耗掉部分 CPU 资源)
(5)kafka Reator 网络模型
(6)充分利用Page Cache
Kafka收到数据后,写磁盘时只是将数据写入Page Cache,并不保证数据一定完全写入磁盘。 并且数据索引 也是保存在内存中,这样数据的写入和查询都是经过内存,性能是非常高的。