消息队列之kafka

读写数据基于topic,每个topic分为多个partition,每个partition有多个副本。每个partition分为多个段文件,在/KaTeX parse error: Expected '}', got 'EOF' at end of input: {topicName}-{partitionid}/目录下存储一个个segment文件。分为log文件和索引文件,索引文件分为按照offset查找文件和按时间查找文件。找到消息是先根据二分法找到消息在哪个段文件(段文件中的消息是按序号递增的),然后根据索引文件找到消息在段文件中大概的位置,再遍历查找到消息。

消费顺序

同个消费组一个partition只能由一个消费者消费,因此只要让需要保证消费顺序的消息,发送到同一个partition,那么消费就是有序的

partition

术语

  • ISR:in-sync replics,每个分区(Partition)中同步的副本列表。一个动态副本列表,当副本与leader已经超过replica.lag.time.max.ms设置的时间没有通信,则会踢出isr。
  • Hight Watermark:副本水位值,表示分区中最新一条已提交(Committed)的消息的Offset。
  • LEO:Log End Offset,Leader中最新消息的Offset。
  • Committed Message:已提交消息,已经被所有ISR同步的消息
  • Lagging Message:没有达到所有ISR同步的消息。

消息发送
可以指定发送到哪个partition,也可以不指定,通过指定一个key,kafka根据这个key计算命中的partition。如果key也没指定就按照Round Robin轮询的方式发送到不同的partition。

leader follower
每个partition都有副本,有个broker中的partition作为leader,若干个broker的partition作为follower,读写数据都基于leader,每次数据变动都会同步到follower。当leader挂了,会基于zk的通知机制,重新选举新的leader,恢复partition正常运行。也就是一个broker可能包含多个partition,某些partition是leader,某些可能其他partition的follower.
分散数据读写到多个broker,因此也提高了吞吐量。

partition在各broker的分配
broker也有一个负责协调的执行者,称为controller,这个broker就负责了partition在broker的分配leader的选举
分配规则:将broker和Partition进行排序,然后将第i个Partition对broker数进行取模命中一个broker,将第i个Partition的第j个副本对broker数进行取模命中一个broker.

partition在消费组中的分配
reblance。消费端也要选一个broker作为分配者,将partition分给各消费者处理。称为coordinator每个消费者都会定期给coordinator发送心跳,如果需要reblance,则会响应对应的信号。同时也可以知道每个消费者存活的情况。消费线程和心跳报告的线程是分开的。

  1. 怎么选择coordinator?offset所在的partition的leader所在的broker作为分配者(offset保存在一个特定topic,通过消费组id进行hash,所以同个消费组的offset都保存在同一个partitio)。
  2. 流程。consumer启动后随便连接一个broker,返回coordinator,consumer发送心跳包给coordinator,coordinator返回IllegalGeneration,说明当前需要reblance重新分配partition,之后该消费者继续
  3. rereblance过程。触发rereblance时,当消费者发送心跳请求过来时,会通知消费者准备进行rereblance,然后从消费者中选一个leader,并把其他消费者信息传给消费者。消费者进行rereblance对partition进行分配,将分配结果发给服务端。服务端将分配结果告诉所有leader分配结果。

触发reblance的情况

  • 增加partition
  • 增加消费者
  • 消费者主动关闭
  • 消费者宕机了
  • coordinator自己也宕机了

参数配置

  • heartbeat.interval.ms参数指定多久发一次心跳
  • session.timeout.ms指定多久没发心跳则认为消费者挂了。
  • max.poll.interval.ms指定两次poll数据最大允许的时间延迟,如果超过了设定的时间,也会认为消费者挂了。

之所以区分两个参数,是因为消费线程和心跳报告的线程是独立的。因此用两个参数来保证消费者是否正常。

leader选举
controller还会负责leader的选举,监控zk broker目录,当有broker挂了,就给受影响的partition从指定目录下找到已经同步了数据的partition副本ISR(in-sync replica已同步的副本),选出新的leader。然后直接发送请求给其他broker通知它们

Controller选举
Controller选举就是依赖zk,每个broker去指定目录创建临时节点,创建成功的broker则称为Controller.

存储
每个partition对应一个文件目录,包含数据和日志文件,因此partition量大时,还需要考虑系统文件句柄数的问题。

partition数量
partition越多吞吐量越高,但是还与broker的传输能力有关系,有相对的上限,需要也提供broker数量。
数量多可能存在其他问起:
1.且broker出现异常,需要恢复的leader数也更多
2.需要分配更多的内存支持批量提交.
3.消息发送后,需要复制到其他副本后,才对客户端可见,所以会导致一定的数据延迟。

消息确定
通过参数request.required.acks可设置消息确认机制

acks说明
0只管发送,不关心broker是否处理成功,可能丢数据
1当写Leader成功后就返回,其他的replica都是通过fetcher去同步的,所以kafka是异步写,主备切换可能丢数据
-1要等到isr里所有机器同步成功,才能返回成功,消息才可见,延时取决于最慢的机器。强一致,不会丢数据。而且如果isr个数小于min.insync.replicas,也会返回不可用

同步的方式
leader提供读写,follower主动定时拉取,不对外提供服务,只是数据备份作用。

即由follower进行拉取同步,而如果request.required.acks设置为-1,则同步到isr中所有broker后消息才提交成功,消费端才可见。

导致副本复制跟不上

  1. 可能是副本机器挂了
  2. 可能是副本io瓶颈,写入速度慢,跟不上从leader拉取消息的速度
  3. 刚启动的副本,还没同步完

leader epoch

offset

通过offset记录某个消费组中某个消费者消费到某个partition的哪个位置。
保存方式
1.0以前保存在zk,但是存在性能问题,之后通过讲offset发送到__consumeroffsetstopic,存储在内存中,消息key由分组id、partition、topic组成,value为偏移量。消息存储在内存中,消息清除的策略是compact,只保留最新的key,其余都清掉。
这个topic默认有50个分区,通过offsets.topic.num.partitions参数指定。基于消费组id进行hash取模。

消息投递语义

kafka 3种消息投递语义,即product在遇到网络异常等情况,消息推送的不同处理。

  • At most once:最多一次,消息可能会丢失,但不会重复。比如消息发送后,收到确认超时不处理,可能消息并没有到达broker,导致消息丢失。
  • At least once:最少一次,消息不会丢失,可能会重复。比如消息发送后,收到确认超时后重发一次,但是实际上消息已经收到了。
  • Exactly once:只且一次,消息不丢失不重复,只且消费一次(0.11中实现,仅限于下游也是kafka)。难保证,需要业务系统配合处理

各种投递方式下的场景:

  • At least once
    先获取数据,再进行业务处理,业务处理成功后commit offset。
    1、生产者生产消息异常,消息是否成功写入不确定,重做,可能写入重复的消息
    2、消费者处理消息,业务处理成功后,更新offset失败,消费者重启的话,会重复消费
  • At most once
    先获取数据,commit offset,再进行业务处理。
    1、生产者生产消息异常,不管,,消息就丢了
    2、消费者处理消息,先更新offset,再做业务处理,做业务处理失败,消费者重启,消息就丢了
  • Exactly once保证刚好一次,则生产者要保证幂等,消息只有一次发送。

保证刚好一次的实现

  1. 序号。broker配置enable.idempotence=true,每个生产者为每个topic的每个partition维护一个递增的序号,发送的消息带上这个序号。broker同样维护producer的最新序号,如果发送的消息的序号不等于最新序号+1,则消息发送失败。如果序号大于最新序号+1,则有可能是乱序后面的消息先到达或者消息有丢失的情况,如果序号小于最新序号+1,则说明消息已经被保存过了。(类似tcp的有序处理)
  2. 事务。由于可能一个事件处理需要把消息发到多个topic,则可以通过事务保证每个消息发送可以一起成功或者一起失败。如果有上游消息,还得保证offset的提交也在一个事务内。kafka的事务是基于两阶段提交形势,消息有准备提交状态、拒绝状态、提交状态,先把消息都发送后,再都改提交状态。未提交的消息,kafka是通过在消费端进行过滤。
重复消费

避免重复消费
1 手动提交offset,在处理一批消息后再提交offset,或者消费一条手动提交一条。
2 最好业务做到幂等。
3 利用事务
4 发送方0.11后通过配置保证发送方的幂等。

事务

kafka提交事务,用于保证消息发到多个topic、offset的手动提交这些操作同时成功。a->b->c,b在消费的时候,事务可以保证offset的提交和发往c的过程同时成功。
事务提交支持异步提交和同步提交。
https://www.jianshu.com/p/3a25a1aaebc1

分区分配策略

Range
rang策略针对单个主题进行分配。分区按照序号排序,消费组中的消费者按注册顺序排序。分配的规则是 分区数%消费者数,余数平均给排在前面的消费者。
比如分区数是3个、消费者是2个,那么消费者1处理分区1和分区2,消费者2处理分区3。
缺点:
如果有多个主题,而且分区数都没办法刚好分配均匀,就会导致前面的消费者在每个主题中都分配到较多的分区,主题越多差异越大。比如2个主题,按照上面例子,消费者1最终负责4个分区,消费者2负责2个分区。

Round Robin
roundRobin策略是将消费组所有订阅的主题的分区进行排序,然后按照轮询的方式将分区分配个每个消费者。如果消费组中的消费者订阅的主题都相同,那么分配则会均匀。如果两个消费者都订阅3个分区的两个主题,则会平均分配到两个消费者上,格子负责3个分区。
如果消费组中消费者订阅的主题不完全相同,则会分配不均。比如消费者1订阅主题1、主题2,消费2订阅主题1,两个主题都有两个分区,那么最终消费者1负责3个分区,消费者2负责主题1的一个分区。这里可以优化的点是,可以把主题1的都交给消费者2,消费者1只消费主题2,这样就能都负责两个分区
缺点
只能保证多主题时,消费组中订阅相同主题的分配均匀。

Sticky
0.11后的版本新引入的粘性策略。这个策略首先保证分配的均匀,其次还会保证分配尽量保持与之前一致
保持与之前一致
比如本来是有三个消费者,有一个消费者挂了之后,此时会把挂了的消费者负责的分区平均到另外两个消费者,而不是全部打乱重新排,避免分区充分配。
均匀分配
相对round robin还会考虑当前消费者当前已经负责消费的partition数,解决Round Robin的缺点。
比如消费者1订阅主题1、主题2,消费2订阅主题1,两个主题都有两个分区,会把主题1的都交给消费者2,消费者1只消费主题2,这样就能都负责两个分区

批量提交

batch.size
通过这个参数来设置批量提交的数据大小,默认是16k,当积压的消息达到这个值的时候就会统一发送(发往同一分区的消息)
linger.ms
表示消息发送后多久后发到broker。默认大小是0ms,表示立即发送。
两个参数条件满足一个即可,所以默认是send之后直接发到broker
缓存池
kafka通过占用固定的内存块,用于缓存发送缓冲数据。通过缓存池的形势,为每个partition发送分配一个内存块,数据发送后,内存块又回到缓存池。如果缓存池满了则阻塞发送操作。通过这样的方式即避免发送缓存占用过多内存,又避免内存重复申请和失败带来的垃圾回收。

消费端拉取方式

消费端只支持拉取模式,不能由broker主动推送。
基于poll方法拉取,可以指定超时等待时间。
基于业务决定长轮询还是短轮询

kafka用zk做了什么

broker topic partition一些元信息会通过zk存储

Broker 列表管理
leader选举
Partition 与 Broker 的关系
Partition 与 Consumer 的关系
Producer 与 Consumer 负载均衡
消费进度 Offset 记录
消费者注册

自动提交

提交时机
每5秒自动提交一次,或者在下一个拉取消息时,提交当前的offset。
通过两个参数配置

  • enable.auto.commit 的默认值是 true;就是默认采用自动提交的机制。
  • auto.commit.interval.ms 的默认值是 5000,单位是毫秒。

存在的问题

  1. 可能offset提交了,当时消息还没处理完,客户端挂了,那么下次启动就丢了部分消息。
  2. 如果消息处理了一些还没提交offset的时候挂了,那么重新启动又从之前的位置开始消费,会导致重复消费的问题。
    所以最好是手动提交来保证消息的可靠
吞吐量

高吞吐量,但是如果一定要保证可靠性,会带来一定的性能损耗,所以鉴于这点,如果要求高可靠性最好还是使用rabbitmq,如果对数据并非那么严谨,但是要吞吐量高,则可以使用kafka。

持久化

顺序写文件的方式,避免随机io提供io速度。依赖文件系统缓存,可以配置达到多少条消息后强制写到文件中,或者配置多少秒刷新一次到文件中.

写文件基于内存映射技术mmap.拿文件基于sendFile从文件内核缓冲区->socket缓存区。

文件按照topic-partition为目录,以一个段为一个文件,每个文件大小超过log.segment.bytes配置的大小就会新加一个文件。文件分为日志文件和索引文件,索引文件用于帮助查找偏移量对应的数据位置。

可靠性

发送端:

  1. 为了防止网络波动导致消息发送丢失,通过kafkaTemplate发送kafka消息可以添加回调,在消息成功发送后,回调通知。
  2. 设置product重试次数和重试间隔

消费端:
自动确认,拿到消息就确认。消费过程失败了,导致消息丢失。改成手动提交offset,但是可能消费完了要提交offset时挂了。导致重复消费。

服务端:
消息发送到leader后,其他副本区leader拉取数据。如果其他副本还没同步完,leader挂了,消息就丢了。

  • 可以通过 acks = all参数,指定发送消息时,消息同步给所有副本后再返回发送成功
  • 默认ack=1,发送给leader后就返回成功,副本再慢慢同步。
  • 设置 replication.factor >= 3 保证最少有3个副本服务才可用
  • 通过min.insync.replicas > 1可以指定同步到几个副本后才返回成功。(leader也算是副本,要设置大于1)。同时也确保
  • replication.factor > min.insync.replicas,否则如果有一个副本挂了,就不能写入,违反高可用。
  • 设置 unclean.leader.election.enable = false,可以让leader选举不会选择未同步完的副本。
消息清理策略

启用删除策略 log.cleanup.policy=delete
直接删除,删除后的消息不可恢复。可配置以下两个策略:

  • 清理超过指定时间清理,log.retention.hours
  • 超过指定大小后,删除旧的消息,log.retention.bytes

压缩策略
按照key进行聚合,每次只保留每个key最新的数据。会出现日志序号不连续的情况,因为某些序号的数据被清楚。
offset保存的topic就是这个策略。

flume

flume可以用于消费kafka,同时支持将消息直接转发给es、kafka等等。
消费kafka的机制是会预先拉取一批消息到内存中,程序消费时去内存中拉取,优化了拉取消息这部分操作的时间。
消费的过程是单线程进行的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值