深入理解Kafka

目录

RabbitMQ和Kafka的区别

RabbitMQ和Kafka的Ack

应用场景

ZooKeeper

是什么

如何实现Leader选举

数据存储原理

Topic

Partition

Replication

Segment

Index

高效读写

顺序读写

零拷贝

单播/多播/消费者组

Offset

同步策略

Ack的三种应答

ISR

如何避免被重复消费

如何保证消息不丢失

总结

 服务端的设置

生产端的设置

消费端的设置

消息积压怎么办?

代码演示

RabbitMQ和Kafka的区别

  1. RabbitMQ是高并发的。
  2. Kafka是高吞吐的,通过Kafka的高吞吐量可以在短时间内收集大量的数据。
  3. Kafka底层使用了零拷贝技术,所以Kafka处理消息的速度是非常快的。

RabbitMQ和Kafka的Ack

  1. RabbitMQ的Ack表示的是消费端的应答。
  2. Kafka的Ack表示的是生产者的应答。

应用场景

  1. Kafka可以用来做分布式架构的日志收集。
  2. 比如日志写入不需要实时性太高,只需要过几分钟把日志写入到数据库里面就行了,这个时候就需要 Kafka的高吞吐来记录日志。
  3. 某些公司用Kafka在微服务中做发布订阅,是因为Kafka的吞吐量非常高,而且底层使用了零拷贝技术, 处理消息的速度是非常快的。

ZooKeeper

是什么

  1. ZooKeeper是一个协调式的数据库,在Kafka中充当调节中枢,Kafka的集群就是由ZooKeeper来进行 管理。
  2. ZooKeeper可以管理集群的节点信息,从而进行Master的选举,还可以利用集群中的有序节点特性, 来实现Master选举。
  3. Kafka就是通过 ZooKeeper来实现集群节点的主从选举。
  4. 总的来说,ZooKeeper 就是分布式数据的一致性解决方案,为分布式应用提供高性能,高可用的分布 式协调服务。
  5. 它的底层是通过基于Paxos算法演化而来的ZAB协议实现的。

 

如何实现Leader选举

  1. ZooKeeper集群节点由三种角色组成,分别是Leader,Follower,Observer。
  2. Leader负责所有事务请求的处理,以及Kafka过半提交时发起的投票和决策。
  3. Follower负责接收客户端的非事务请求,另外Follower节点还会参与Leader的选举投票。
  4. Observer也是负责接收客户端的非事务请求,但是Observer节点不会参与任何选举投票,只是为了扩 展ZooKeeper集群来分担读操作的压力。
  5. 其次ZooKeeper集群是一种典型的中心化架构,也就是会有一个Leader作为决策节点,专门负责事务 请求的处理和数据的同步。
  6. 这种架构的好处是可以减少集群架构里面数据同步的复杂度,集群管理会更加简单和稳定。
  7. 但是会带来Leader选举的一个问题,也就是说,如果Leader节点宕机了,为了保证集群继续提供可靠 的服务,ZooKeeper需要从剩下的Follower节点里面去选举一个新的节点作为Leader节点,也就是 Leader选举。
  8. 具体的实现是,每一个节点都会向集群里面的其他节点发送一个票据Vote。
  9. 这个票主要据包括epoch,zxid,myid三种属性。
  1. epoch是逻辑时钟,用来表示当前票据是否过期。
  2. zxid是事务id,表示当前节点最新数据的事务编号。
  3. myid是服务器id,就是在myid文件里面填写的数字。
  1. 每个节点都会选自己当Leader,所以第一次投票的时候携带的是当前节点的信息。
  2. 接下来每个节点用收到的票据和自己的节点票据做比较,根据epoch,zxid,myid的顺序逐一比较,以 值最大的一方获胜。
  3. 比较结束以后这个节点下次再投票的时候,发送的投票请求就是获胜的票据信息。
  4. 经过了多轮投票以后,每个节点都会去统计当前达成一致的票据,以少数服从多数的方式,最终获得 票据最多的节点成为Leader。  
  5. 选择epoch,zxid,myid作为投票评判依据的原因,我是这么理解的。
  6. 选择epoch是因为网络通信可能会有延迟,有可能在新一轮的投票里面收到上一轮投票的票据,这种 数据应该丢弃,否则会影响投票的结果和效率。
  7. 选择zxid是因为zxid越大,就说明这个节点的数据越接近Leader,所以用zxid做判断条件是为了避免 数据丢失的问题。
  8. 选择myid是因为myid能避免投票的时间过长,直接用myid的最大值来快速选择投票的结果。

数据存储原理

Topic

  1. 在Kafka中,用来存储消息的队列叫做Topic,是一个逻辑概念,可以理解为一组消息的集合。
  2. 生产者和Topic以及Topic和消费者的关系都是多对多。一个生产者可以发送消息到多个Topic,一个 消费者也可以从多个Topic获取消息(但是不建议这么做)。
  3. 生产者发送消息时,如果Topic不存在,Kafka 默认会自动创建。

Partition

  1. 首先,Kafka为了实现横向扩展,它会把不同的数据存放在不同的Broker上。
  2. 同时为了降低单台服务器的访问压力,把一个Topic中的数据分隔成多个Partition。在服务器上,每个 Partition都有一个物理目录,Topic名字后面的数字标号即代表分区。比如创建一个名为myTopic的主 题,假设数据目录被分布到了3台机器。
  3. myTopic-0表示分区A节点,myTopic-1表示分区B节点,myTopic-2表示分区C节点。

Replication

  1. Kafa为了提高分区的可靠性,又设计了副本机制。
  2. 我们创建Topic的时候,通过指定replication-factor副本因子,来确定Topic 的副本数。
  3. 副本因子数必须小于等于节点数,否则会报错。这样就可以保证,绝对不会有一个分区的两个副本分 布在同一个节点上,不然副本机制也失去了备份的意义。
  4. 假设现在创建了一个3个分区3个副本,然后把它们均匀的分布到了3个Broker节点上,每个Broker 节点互为备份。
  5. 这些所有的副本分为两种角色,Leader 对外提供读写服务。Follower唯一的任务就是从Leader异步拉 取数据。
  6. 图中红色的副本为Leader,同时Leader也被均匀的分布在各个节点上,可以保证读写均匀。

Segment

  1. Kakfa为了防止Log不断追加导致文件过大,导致检索消息效率变低。
  2. 在一个Partition的Log超出一定大小的时候,就被切割为多个Segment来组织数据。
  3. 在磁盘上,每个Segment由一个log文件和2个Index文件组成。
  4. 这三个文件是成套出现的。
  5. 其中.index是用来存储Consumer的Offset偏移量的索引文件,.timeindex是用来存储消息时间戳的索 引文件,.log文件就是用来存储具体的数据文件。
  6. 以切割时记录的Offset值作为文件的名字。它的文件结构是这样的,如图所示:

Index

  1. Index其中一种是偏移量索引文件,记录的是Offset和消息在Log文件中的位置映射关系。
  2. 还有一种是时间戳索引文件,记录的是时间戳和Offset的关系。
  3. 为了提高检索效率Kafka并不会为每一条消息都建立索引,而是采用稀疏索引。也就是说每产生一批 消息的时候才产生一条索引记录。
  4. 可以通过参数来设置索引的稀疏程度。
  5. 越稠密的索引检索数据越快,但是会消耗更多的存储空间。
  6. 越稀疏的索引占用存储空间越小,但是插入和删除时维护开销也小。
  7. 时间戳索引也是采用稀疏索引设计。由于索引文件是以Offset命名的,所以Kafka在检索数据的时候, 是采用二分法查找,效率就非常快。

高效读写

  1. 批量发送是指线程A把数据都写入到数据池,线程B把数据池里面的数据批量发送给Broker。
  2. 批量发送不是由Kafka实现的,是由.Net提供的Kafka组件来实现的。
  3. 当数据池里面的数据达到阈值之后就会向Kafka批量发送,阀值是在客户端配置的。

顺序读写

Kafka是消息队列,数据只有新增没有修改,所以可以连续。Kafka的数据可以预置,默认数据只能存储7天,如果消费端出现了问题超过了7天数据都没有被消费,那么数据也会被删除。如果遇到特殊的情况,可以改变默认值。

零拷贝

  1. 在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上,那么它必须要经过几个 拷贝的过程。
  2. 从磁盘中读取目标文件内容拷贝到内核缓冲区。
  3. CPU控制器再把内核缓冲区的数据赋值到用户空间的缓冲区中。
  4. 接着在应用程序中,调用write方法,把用户空间缓冲区中的数据拷贝到内核中。
  5. 最后,把在内核模式下的数据赋值到网卡缓冲区,网卡缓冲区再把数据传输到目标服务器上。
  6. 在这个过程中我们可以发现,数据从磁盘到最终发送出去,要经历4次拷贝。
  7. 而在这四次拷贝过程中,有两次拷贝是浪费的,分别是:
  1. 从内核空间赋值到用户空间。
  2. 从用户空间再次复制到内核空间。
  1. 除此之外,由于用户空间和内核空间的切换会带来CPU的上下文切换,对于CPU的性能也会造成影响。
  2. 而零拷贝,就是把这两次多于的拷贝省略掉,应用程序可以直接把磁盘中的数据从内核中直接传输给 Socket,而不需要再经过应用程序所在的用户空间。
  3. 零拷贝通过DMA技术把文件内容复制到内核空间中的Read Buffer。
  4. 接着把包含数据的文件描述符加载到Socket Buffer中,DMA引擎直接可以把数据从内核空间中传递给 网卡设备。
  5. 在这个流程中,数据只经历了两次拷贝就发送到了网卡中,并且减少了2次CPU的上下文切换,对于 效率有非常大的提高。
  6. 所以说所谓的零拷贝,并不是完全没有数据的赋值,只是相对于用户空间来说,不再进行数据拷贝。
  7. 对于整个流程来说,零拷贝只是减少了不必要的拷贝次数。

单播/多播/消费者组

  1. 单播类似于队列,一个消息只能被消费一次,消费过了,其它消费者就不能消费了。
  2. 多播类似于发布订阅,一个消息可以被多个消费者同时消费。
  3. 消费者组是指在一个消费者组中,每一个Topic中的消息只能被这个组中的一个消费者消费。

Offset

  1. Offset就是偏移量,也就是当前数据的唯一ID。
  2. 自增而且在一个分区里面存在顺序性,但是多个分区里面没有办法保证顺序性,顺序性指的就是偏移量。

同步策略

  1. 为了保证Producer发送的数据,能可靠的到达指定的Topic,Topic的每个Partition收到Producer发送 的消息后,都需要向Producer发送Ack确认收到,如果Producer收到Ack,就会进行下一轮的发送, 否则重新发送数据。
  2. 那么何时发送Ack呢?
  3. 确保有Follow与Leader同步完成,Leader再发送Ack,这样才能保证Leader挂掉之后,能在Follower 中选举出新的Leader。
  4. 那么有多少个Follower同步完成之后发送Ack呢?
  5. 第一种方案是,半数以上的Follower同步完成,就可以发送Ack。
  6. 第二种方案是,全部的Follower同步完成,才可以发送Ack。

Ack的三种应答

  1. Acks=0是效率最高的,一旦接收到数据就立刻返回Ack,但是Leader挂了或者数据没有落盘的时候, 就有可能丢失数据。
  2. Acks=1是Leader落盘成功之后返回Ack,但是有可能刚落盘成功Leader就挂了。
  3. Acks=-1是效率最低的,需要Partition的Leader和Follower全部落盘成功之后才返回Ack,能保证数据 不丢失,但是会存在数据重复的问题。当数据写入Leader和Follower的时候,Leader挂了,然后Follower 成了Leader,但是没有给生产者返回Ack,这个时候生产者补偿重试,又会向Follower写入一条数据, 所以要保证数据的幂等性。原理就是根据客户端的ClientID和当前消息的唯一ID,但是如果客户端也 重启了,那么数据还是会重复,这是因为ClientID变了。

ISR

  1. 发送到Kafka Broker上面的消息,最终是以Partition的物理形态来存储到磁盘上的。
  2. 而Kafka为了保证Parititon的可靠性,提供了Paritition的副本机制,然后在这些Partition的副 本集里面。存在Leader Partition和Flollower Partition。
  3. 生产者发送过来的消息,会先存到Leader Partition里面,然后再把消息复制到Follower   Partition。
  4. 这样设计的好处就是一旦Leader Partition 所在的节点挂了,可以重新从剩余的Partition副本里 面选举出新的Leader。
  5.  然后消费者可以继续从新的Leader Partition里面获取未被消费的数据。
  6. 在Partition 多副本设计的方案里面,有两个很关键的需求。 副本数据的同步 新Leader的选举
  7. 这两个需求都需要涉及到网络通信,Kafka 为了避免网络通信延迟带来的性能问题,以及尽可能的保 证新选举出来的Leader Partition 里面的数据是最新的,所以设计了ISR 这样一个方案。
  8. ISR全称是in-sync replica,它是一个集合列表,里面保存的是和Leader Parition节点数据最接近 的Follower Partition。
  9. 如果某个Follower Partition里面的数据落后Leader太多,就会被剔除ISR列表。
  10. 简单来说,ISR 列表里面的节点,同步的数据一定是最新的,所以后续的Leader选举,只需要从ISR 列表里面筛选就行了。
  11. 引入ISR 这个方案的原因主要有两个。
  12. 尽可能的保证数据同步的效率,因为同步效率不高的节点都会被踢出ISR列表。
  13. 避免数据的丢失,因为ISR里面的节点数据是和Leader副本最接近的。

如何避免被重复消费

  1. 首先,Kafka的Broker上面存储的消息都有一个Offset标记。
  2. 然后Kafka的消费者是通过Offset这个标记,来维护当前已经消费的数据。
  3. 然后消费者每消费一批数据,Kafka的Broker就会更新Offset值,避免重复消费的问题。
  4. 默认情况下,消息消费完成以后会自动提交Offset值,避免重复消费。
  5. Kafka的消费端自动提交的时候,会有一个默认5秒的时间间隔。
  6. 也就是说在5秒之后向Broker去获取消息的时候,来实现Offset的提交。
  7. 所以Consumer在消费过程中,应用程序强制被kill掉或者宕机的时候,可能会导致Offset没有提交, 从而产生消息重复。
  8. 在Kafka里面有一个叫Partition Balance的机制,就是把多个Partition均匀分配给多个消费者。
  9. 那么Consumer端会从默认的Partition里面去消费消息。
  10. 如果Consumer在默认的5秒钟内,没有办法处理完这一批消息,就会触发Kafka的ReBalance机制, 从而导致Offset自动提交失败。
  11. 而在重新ReBalance以后,Consumer还是会从之前没有提交的Offset位置开始消费,从而导致重复消 费的一个问题。
  12. 我们可以提高消费端处理性能,去避免触发Balance,比如可以用异步的方式来处理消息。缩短单个 消息的消费时长。
  13. 还可以调整消费端消息处理的一个超时时间,我们可以把超时时间设置长一点。
  14. 还可以减少一次性从Broker上获取的消息条数。
  15. 还可以针对每一条消息去生成一个MD5的值,然后保存到数据库或者Redis里面,在处理消息之前先 去数据库或者Redis里面判断是否已经存在相同消息的MD5值,如果存在那么就不消费。

如何保证消息不丢失

总结

  1. 保证消息不丢失可以把服务端的持久化,设置为同步刷盘。
  2. 还可以把生产端设置为同步投递。
  3. 还可以把消费端设置为手动提交。

 服务端的设置

 

  1. 服务端设置Broker中的配置项unclean.Leader.election.enable = false,保证所有副本同步。
  2. Producer将消息投递到服务器的时候,也需要将消息持久化,同步到磁盘。
  3. 同步到硬盘的过程中,会有同步刷盘和异步刷盘。如果选择的是同步刷盘,一定会保证消息不丢失。 就算刷盘失败,也可以及时补偿。如果选择的是异步刷盘,消息会有一定概率丢失。
  4. 网上有一种说法,说Kafka不支持同步刷盘,这种说法也不能说是错的。
  5. 可以通过参数的配置变成同步刷盘:
  6. # 达到一定消息数量时,将数据flush到日志文件中。
  7. #log.flush.interval.messages=10000
  8. # 达到一定的时间(ms)间隔时,执行一次强制的flush操作。
  9. interval.ms和interval.messages 无论哪个达到,都会flush。
  10. #log.flush.interval.ms=1000

生产端的设置

  1. 生产端的设置就是Producer使用带回调通知的send(msg,callback)方法,并且设置Acks=All。
  2. Producer要保证消息到达服务器,就需要使用到消息确认机制,也就是说,必须要确保消息投递到服 务端,并且得到投递成功的响应,确认服务器已接收,才会继续往下执行。
  3. 如果Producer将消息投递到服务端,服务端没来得及接收就已经宕机了,那投递过来的消息岂不是丢 失了。
  4. 所以在Producer投递消息时,都会记录日志,然后再将消息投递到服务端,就算服务器宕机了,等 服务器重启之后,也可以根据日志信息完成消息补偿,确保消息不丢失。

消费端的设置

  1. 消费端的设置就是修改enable.auto.commit=false。
  2. 在Kafka中,消费者消费完成之后,它不会立即删除,而是使用定时清除策略。
  3. 也就是说,消费者要确保消费成功之后,手动Ack提交。如果消费失败的情况下,要不断地进行重试。
  4. 所以消费端不要设置自动提交,设置为手动提交才能保证消息不丢失。

消息积压怎么办?

  1. Kafka一般消息默认过期时间是7天,7天之后没有消费的消息会自动删除。
  2. 如果是消费能力不足,可以提高对应Topic的分区数,同时提升消费者组的消费者数量。
  3. 如果是消费间隔较长导致的,可以提高每次拉取数据的数量。

代码演示

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子丶鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值