前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kafka分区与消费者的关系kafka分区和消费者线程的关系

Kafka分区与消费者的关系kafka分区和消费者线程的关系

作者头像
chenchenchen
发布2022-03-09 13:09:25
3.8K0
发布2022-03-09 13:09:25
举报
文章被收录于专栏:chenchenchenchenchenchenchenchenchen

分区(partition)

kafka中的topic可以细分为不同的partition,一个topic可以将消息存放在不同的partition中。

leader和follower

每个partition可以设置一个leader和多个follower。kafka的消息没有设置读写分离,每个消息发送时,都是发送至对应的partition的leader-paertion,follower-partition主要是为了备份数据而存在,当leader-partition出现故障时,数据已经完全同步的follower-partition也会切换成leader-partition。

AR和ISR

AR:分区中所有的副本统称为AR。 ISR:所有与leader节点保持同步的副本(包括leader节点)组成的节点,生产者首先将消息发送给leader副本,然后follower从leader中同步消息。 ISR是AR的子集。

数据的存储

在partition中,一个topic中的数据存放在不同的partition中,一个分区的内容会存储成一个log文件,为了防止log过大,引入了日志分段,根据一定规则将log切分为多个logSegment,相当于一个巨型文件被切分成了很多不同的文件。log和logSegment关系如下:

Log在物理上只以文件夹的形式存储,日志文件在磁盘的存储如下:

主题的分区数设置

在server.properties配置文件中可以指定一个全局的分区数设置,这是对每个主题下的分区数的默认设置,默认是1。

当然每个主题也可以自己设置分区数量,如果创建主题的时候没有指定分区数量,则会使用server.properties中的设置。

bin/kafka-topics.sh --zookeeper localhost:2181 --create --topic my-topic --partitions 2 --replication-factor 1

在创建主题的时候,可以使用--partitions选项指定主题的分区数量

[root@localhost kafka_2.11-2.0.0]# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic abc
Topic:abc       PartitionCount:2        ReplicationFactor:1     Configs:
        Topic: abc      Partition: 0    Leader: 0       Replicas: 0     Isr: 0
        Topic: abc      Partition: 1    Leader: 0       Replicas: 0     Isr: 0

分区多的优点

kafka使用分区将topic的消息打散到多个分区,分别保存在不同的broker上,实现了producer和consumer消息处理的高吞吐量。

Kafka的producer和consumer都可以多线程地并行操作,而每个线程处理的是一个分区的数据。因此分区实际上是调优Kafka并行度的最小单元。对于producer而言,它实际上是用多个线程并发地向不同分区所在的broker发起Socket连接同时给这些分区发送消息;而consumer,同一个消费组内的所有consumer线程都被指定topic的某一个分区进行消费。

所以说,如果一个topic分区越多,理论上整个集群所能达到的吞吐量就越大。

分区不是越多越好

分区是否越多越好呢?显然也不是,因为每个分区都有自己的开销:

一、客户端/服务器端需要使用的内存就越多

客户端producer有个参数batch.size,默认是16KB。它会为每个分区缓存消息,一旦满了就打包将消息批量发出。

分区越多,consumer端获取数据所需的内存越多。同时consumer线程数要匹配分区数(大部分情况下是最佳的消费吞吐量配置)的话,那么这里面的线程切换的开销本身已经不容小觑了。服务器端的很多组件都在内存中维护了分区级别的缓存,比如controller,FetcherManager等,因此分区数越多,这种缓存的成本就越大。

二、文件句柄的开销

每个分区在底层文件系统都有属于自己的一个目录。该目录下通常会有两个文件: base_offset.log和base_offset.index。Kafak的controller和ReplicaManager会为每个broker都保存这两个文件句柄(file handler)。很明显,如果分区数越多,所需要保持打开状态的文件句柄数也就越多,最终可能会突破你的ulimit -n的限制。

三、降低高可用性

Kafka通过副本(replica)机制来保证高可用。具体做法就是为每个分区保存若干个副本(replica_factor指定副本数)。每个副本保存在不同的broker上。

如果你有10000个分区,10个broker,也就是说平均每个broker上有1000个分区。此时这个broker挂掉了,那么zookeeper和controller需要立即对这1000个分区进行leader选举。比起很少的分区leader选举而言,这必然要花更长的时间,并且通常不是线性累加的。如果这个broker还同时是controller情况就更糟了。

如何确定分区数量呢

可以遵循一定的步骤来尝试确定分区数:创建一个只有1个分区的topic,然后测试这个topic的producer吞吐量和consumer吞吐量。假设它们的值分别是Tp和Tc,单位可以是MB/s。然后假设总的目标吞吐量是Tt,那么分区数 = Tt / max(Tp, Tc)

说明:Tp表示producer的吞吐量。测试producer通常是很容易的,因为它的逻辑非常简单,就是直接发送消息到Kafka就好了。Tc表示consumer的吞吐量。测试Tc通常与应用的关系更大, 因为Tc的值取决于你拿到消息之后执行什么操作,因此Tc的测试通常也要麻烦一些。

kafka分区和消费者线程的关系

1、要使生产者分区中的数据合理消费,消费者的线程对象和分区数保持一致,多余的线程不会进行消费(会浪费)

2、消费者默认即为一个线程对象 ;

3、达到合理消费最好满足公司:消费者服务器数*线程数 = partition个数

生产者与分区(多对多)

默认的分区策略是:

  • 如果在发消息的时候指定了分区,则消息投递到指定的分区
  • 如果没有指定分区,但是消息的key不为空,则基于key的哈希值来选择一个分区
  • 如果既没有指定分区,且消息的key也是空,则用轮询的方式选择一个分区

分区与消费者(多对一)

同一时刻,一条消息只能被组中的一个消费者实例消费

消费者组订阅一个主题,意味着主题下的所有分区都会被组中的消费者消费到,并且主题下的每个分区只从属于组中的一个消费者,不可能出现组中的两个消费者负责同一个分区

如果分区数大于或者等于组中的消费者实例数,那么一个消费者会负责多个分区;如果消费者实例的数量大于分区数,有一些消费者是多余的,一直接不到消息而处于空闲状态。

即:

  • 若consumer数量大于partition数量,会造成限制的consumer,产生浪费。
  • 若consumer数量小于partition数量,会导致均衡失效,其中的某个或某些consumer会消费更多的任务。

为什么一个消费者可以消费多个分区,但是一个分区不能被多个消费者消费呢?

就是要保证一个分区下的消息顺序性。倘若,两个消费者负责同一个分区,那么就意味着两个消费者同时读取分区的消息,由于消费者自己可以控制读取消息的offset,就有可能C1才读到2,而C1读到1,C1还没处理完,C2已经读到3了,则会造成很多浪费,因为这就相当于多线程读取同一个消息,会造成消息处理的重复,且不能保证消息的顺序,这就跟主动推送(push)无异。

所以说消息积压的时候,部署多台消费者实例是不能加快消费原有分区的消息的。最多增加到和partition数量一致,超过的组员只会占用资源,而不起作用。

kafka官方文档:https://kafka.apache.org/documentation.html#introduction

通过在主题中具有并行性--分区--的概念,Kafka能够为用户进程池提供排序保证和负载平衡。这是通过将主题中的分区分配给使用者组中的使用者来实现的,这样每个分区就会被组中的一个消费者使用。通过这样做,我们确保使用者是该分区的唯一读者,并按顺序使用数据。由于有许多分区,这仍然平衡了许多使用者实例的负载。但是,请注意,不能有比分区更多的使用者实例。

消费者(consumer)

分组(group)

消费者从partition中消费数据,consumer有group的概念,每个group可以消费完整的一份topic中的数据。

消费者分区分配策略

range策略

是默认的分配策略,是基于每个主题的。

1、range分配策略针对的是主题(这里所说的分区指的某个主题的分区,消费者值的是订阅这个主题的消费者组中的消费者实例)

2、首先,将分区按数字顺序排行序,消费者按消费者名称的字典序排好序

3、然后,用分区总数除以消费者总数。如果能够除尽平均分配;若除不尽,则位于排序前面的消费者将多负责一个分区

假设有3个主题(T1,T2,T3),都有7个分区,那么按照咱们上面这种Range分配策略分配后的消费结果如下:

消费者线程

对应消费的分区序号

C0-0

T1(0,1,2),T2(0,1,2),T3(0,1,2)

C1-0

T1(3,4),T2(3,4),T3(3,4)

C1-1

T1(5,6),T2(5,6),T3(5,6)

roundrobin(轮询)

RoundRobin策略的原理是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询算法逐个将分区以此分配给每个消费者。

使用RoundRobin分配策略时会出现两种情况:

  • 如果同一消费组内,所有的消费者订阅的消息都是相同的,那么 RoundRobin 策略的分区分配会是均匀的。
  • 如果同一消费者组内,所订阅的消息是不相同的,那么在执行分区分配的时候,就不是完全的轮询分配,有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个 topic,那么在分配分区的时候,此消费者将不会分配到这个 topic 的任何分区。

因此在使用RoundRobin分配策略时,为了保证得均匀的分区分配结果,需要满足两个条件:

  1. 同一个消费者组里的每个消费者订阅的主题必须相同;
  2. 同一个消费者组里面的所有消费者的num.streams必须相等。

我们分别举例说明:

第一种:比如我们有3个消费者(C0,C1,C2),都订阅了2个主题(T0 和 T1)并且每个主题都有 3 个分区(p0、p1、p2),那么所订阅的所有分区可以标识为T0p0、T0p1、T0p2、T1p0、T1p1、T1p2。此时使用RoundRobin分配策略后,得到的分区分配结果如下:

消费者线程

对应消费的分区序号

C0

T0p0、T1p0

C1

T0p1、T1p1

C2

T0p2、T1p2

第二种:比如我们依然有3个消费者(C0,C1,C2),他们合在一起订阅了 3 个主题:T0、T1 和 T2(C0订阅的是主题T0,消费者C1订阅的是主题T0和T1,消费者C2订阅的是主题T0、T1和T2),这 3 个主题分别有 1、2、3 个分区(即:T0有1个分区(p0),T1有2个分区(p0、p1),T2有3个分区(p0、p1、p2)),即整个消费者所订阅的所有分区可以标识为 T0p0、T1p0、T1p1、T2p0、T2p1、T2p2。此时如果使用RoundRobin分配策略,得到的分区分配结果如下:

消费者线程

对应消费的分区序号

C0

T0p0

C1

T1p0

C2

T1p1、T2p0、T2p1、T2p2

Sticky分配策略

在kafka的0.11.X版本才开始引入的,是目前最复杂也是最优秀的分配策略。

S它的设计主要实现了两个目的,如果这两个目的发生了冲突,优先实现第一个目的:

  1. 分区的分配要尽可能的均匀;
  2. 分区的分配尽可能的与上次分配的保持相同。

我们有3个消费者(C0,C1,C2),都订阅了2个主题(T0 和 T1)并且每个主题都有 3 个分区(p0、p1、p2),那么所订阅的所有分区可以标识为T0p0、T0p1、T0p2、T1p0、T1p1、T1p2。此时使用Sticky分配策略后,得到的分区分配结果如下:

消费者线程

对应消费的分区序号

C0

T0p0、T1p0

C1

T0p1、T1p1

C2

T0p2、T1p2

看起来和前面RoundRobin分配策略一样,但其实底层实现并不一样。

这里假设C2故障退出了消费者组,然后需要对分区进行再平衡操作。

如果使用的是RoundRobin分配策略,它会按照消费者C0和C1进行重新轮询分配,再平衡后的结果如下:

消费者线程

对应消费的分区序号

C0

T0p0、T0p2、T1p1

C1

T0p1、T1p0、T1p2

但是如果使用的是Sticky分配策略,再平衡后的结果会是这样:

消费者线程

对应消费的分区序号

C0

T0p0、T1p0、T0p2

C1

T0p1、T1p1、T1p2

Stiky分配策略保留了再平衡之前的消费分配结果,并将原来消费者C2的分配结果分配给了剩余的两个消费者C0和C1,最终C0和C1的分配还保持了均衡。这时候再体会一下sticky(翻译为:粘粘的)这个词汇的意思,是不是豁然开朗了。

对于同一个分区而言有可能之前的消费者和新指派的消费者不是同一个,对于之前消费者进行到一半的处理还要在新指派的消费者中再次处理一遍,这时就会浪费系统资源。而使用Sticky策略就可以让分配策略具备一定的“粘性”,尽可能地让前后两次分配相同,进而可以减少系统资源的损耗以及其它异常情况的发生。

分区Rebalance(再均衡)场景

  • 有新的消费者加入消费者群组
  • 已有的消费者退出消费者群组
  • 订阅的主题的分区发生变化

Rebalance给消费者群组带来了高可用性与伸缩性,但是在Rebalance期间,消费者无法读取消息,整个群组一小段时间不可用,而且当分区被重新分配给另一个消费者时,消费者当前的读取状态会丢失。

消费者组(广播模式)

如果想实现广播的模式就需要设置多个消费者组,这样当一个消费者组消费完这个消息后,丝毫不影响其他组内的消费者进行消费,这就是广播的概念。

(1)多个消费者组,1个partition

该topic内的数据被多个消费者组同时消费,当某个消费者组有多个消费者时也只能被一个消费者消费,如图4所示:

kafka中partition数量与消费者对应关系以及Java实践
kafka中partition数量与消费者对应关系以及Java实践

(2)多个消费者组,多个partition

该topic内的数据可被多个消费者组多次消费,在一个消费者组内,每个消费者又可对应该topic内的一个或者多个partition并行消费,如图5所示:

kafka中partition数量与消费者对应关系以及Java实践
kafka中partition数量与消费者对应关系以及Java实践

参考:

Kafka分区与消费者的关系:https://www.cnblogs.com/cjsblog/p/9664536.html

Kafka分区数与消费者个数:https://www.jianshu.com/p/dbbca800f607https://blog.csdn.net/OiteBody/article/details/80595971

kafka分区和消费者线程的关系:https://blog.csdn.net/tankun940507994/article/details/72781996

深入分析Kafka架构(三):消费者消费方式、三种分区分配策略、offset维护:https://blog.csdn.net/qq_26803795/article/details/105562691

kafka中partition数量与消费者对应关系以及Java实践:https://www.tqwba.com/x_d/jishu/279556.html

为什么不能有比分区更多的使用者实例?:https://stackoverflow.com/questions/25896109/in-apache-kafka-why-cant-there-be-more-consumer-instances-than-partitions

kafka多个消费者消费一个topic_详细解析kafka之 kafka消费者组与重平衡机制:https://blog.csdn.net/weixin_39737224/article/details/112073632

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分区(partition)
    • leader和follower
      • AR和ISR
        • 数据的存储
          • 主题的分区数设置
            • 分区多的优点
            • 分区不是越多越好
            • 如何确定分区数量呢
            • kafka分区和消费者线程的关系
        • 生产者与分区(多对多)
        • 分区与消费者(多对一)
        • 消费者(consumer)
          • 分组(group)
            • 消费者分区分配策略
              • range策略
              • roundrobin(轮询)
              • Sticky分配策略
            • 消费者组(广播模式)
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档