Kafka技术研究简报

Kafka技术研究简报

前言:
技术研究简报针对于引进未使用过的技术时,对引进的技术进行初步预研。
从引进技术初期记录简单研究成果,到后续对技术有更深入的理解,都应当于本文档进行更新。

1. 技术简介

官网:http://kafka.apache.org/
中文参考网:http://ifeve.com/kafka-1/

2. 核心概念

2.1 主题(Topic)

Topic是Kafka用于发布订阅消息的基础单位,它是Kafka的消息记录流。

2.2 生产者

Kafka是典型的发布订阅模式的消息队列组件,即消息由生产者进行发布,由消费者进行消费。
Kafka提供了kafka-clients,里面封装了生产者API和消费者API,我们可以使用生产者API向Kafka发布消息,常用的Spring-Kafka中便很好的集成了kafka-clients。

2.3 消费者

消费者以消费群(group)来标识自己,group-id相同的消费者属于一个群组。group的主要作用在于Kafka能更加有效的将消息均匀分配给group中的消费者,且topic中的每一条消息,只会被相同group中的一个消费者消费掉。若多个消费者监听了同一个topic,但这些消费者都属于不同的group,那么这个topic中的每一条消息都会被广播至这些消费者。

2.4 server和broker

Kafka支持部署多个节点,不同节点可部署至不同服务器上或相同服务器上。Kafka的服务端配置文件默认为server.properties,这个配置文件中会对当前节点配置broker-id,节点之间的broker-id不能相同。通常情况为了更方便确认每个节点id,server.properties文件命名会跟随broker-id,如:broker-1的节点,其配置文件往往命名为server-1.properties,因此,当描述Kafka集群中的节点时,使用server或broker都行。

2.5 Partition和Log

Topic的消息数据在Kafka中是以日志的形式写入磁盘的,而日志会以分区划分目录,分区数量来源于Topic的配置,在Topic创建时可指定该Topic的分区数量,Kafka会将消息日志分配至这些分区中,每个分区都会存储这个Topic的部分消息。分区的命名原则以0开始递增。如:某Topic命名为test,为其配置了3个分区,则该Topic的消息日志会写入进test-0,test-1,test-2三个分区目录中。

2.6 副本和副本因子

Topic在创建时可配置副本因子(Replication-factor),指定副本因子后,Topic的分区将会根据其值进行备份。副本因子数 = 1 + 副本数量,其中1代表对外读写的分区(leader),剩余的分区将作为副本进行实时备份(follower)。如配置副本因子数为2,则该Topic的每个分区都有一个leader和一个follower。
注意:分区因子数不能小于1,且不能大于broker节点数量。

3. 关键技术

3.1生产者消息的分区写入策略

Kafka的分区概念引入使得Kafka能够承受海量的并发,主要原因在于消息的分区使得读写操作有效实现了负载均衡。
当Topic的分区只有一个的时候,那么所有发布至这个Topic的消息都会写入至这一个分区,若分区数量不止一个,Kafka会根据具体策略将消息写入对应的分区。Kafka支持的分区策略有轮询策略(Round-robin)、随机、以及根据key值决定。
轮询:即按顺序进行写入,第一条消息写进第一个分区,第二条消息写进第二个分区,如此类推。
Key值:生产者API提供了指定key值的发布消息方法,若发布至Topic的消息携带了key值,那么相同key值的消息会被写入进相同的分区中。Kafka内部实现了算法用于计算key值如何对应哪个分区。
Kafka的默认策略:Kafka默认的策略实际上包含了key值写入和轮询写入,即当消息携带key时,则写入对应的分区,若消息中没有key值,则使用轮询写入。

3.2 ISR(In-Sync Replicas)

ISR即保持同步的副本。由于消息被分布在各个分区中,在实际运行中难免会出现Kafka节点宕机或其它问题,导致消息丢失,因此Kafka使用ISR技术保证数据的高可用性。
ISR原理即:作为副本的分区,会不停的从主分区(对外读写的分区)拉取最新消息并写入,如此实现数据的实时备份。其中对外提供读写的分区则担当起leader的责任,而ISR列表里需要同步的副本则为follower

3.3 Kafka的选举机制

根据3.2中描述,Topic的每个分区,只有leader会对外保持读写,即生产者的消息写进leader分区里,消费者也是从leader分区进行读取消费。那么如果作为leader的broker节点突然死机,则原本计划写进这个分区的消息则无法继续写入,并且已写入但未被消费的消息则无法继续被消费者消费。
为防止这种问题,Kafka推出了选举机制,即当leader节点故障,Kafka会从ISR列表里剩下的follower中,选举出一位作为新的leader,以保证分区消息的正常读写。
因此,在实际的运用中,至少需要保证Topic的分区有至少一个副本(副本因子数为2),且broker节点部署至不同服务器上,才能保证ISR和选举机制的有效。

3.4 节点、分区、副本的关系

在这里插入图片描述

如上图所示,分区是对topic的数据进行分区存储,副本是对每个分区进行的备份。即,leader和follower都是分区,只不过各自扮演了不同的角色,leader负责对外读写数据,follower只负责从leader同步备份数据。

3.5 分区和副本在broker下的分布

以3.3图为例,Topic分区3,副本因子2,则实际上,这个topic有总计6份分区用于存储消息。
Kafka的分布原则主要有以下两点:

  1. Kafka会尽量将分区均匀分布至各个broker节点。
  2. 每个broker都有获得leader的机会,Kafka也会尽量将leader均匀分布给各个broker。

当对某个Topic进行分区或副本的数量调整时,Kafka的分配原则主要如下:

  1. Kafka会尽量保证原有的分区位置不变,即尽量不出现分区从一个broker节点迁移至另一个broker节点的情况。
  2. 在第一点得到保证时,Kafka会尽量遵循均匀分布的原则,将分区均匀分配给broker。

Tips-1: Kafka不支持减少分区,因为不同分区存储的消息数据不一致,若贸然减少分区,则重新分配消息将会伴随着极高的风险和极大的成本。
Tips-2: 尽量避免减少副本,副本的减少会降低数据的可用性,同时Kafka是否支持减少副本,这一点有待考证

3.6 acks

为了进一步提升数据的高可用性,防止数据丢失,Kafka推出了acks机制。
acks在生产者进行配置,可配置的范围:0, 1, all
0:生产者将消息发送出去即视为发送成功,不管数据是否被leader成功写入或被副本成功同步
1:生产者的默认配置,数据写入leader中即视为发送成功,不管数据是否被同步至follower副本
all:ISR列表里的所有follower副本都同步成功,生产者的消息才算发送成功。以3.3的图举例:生产者向topic发送一条消息,该条消息被分配至partition-0,首先会写进作为leader的分区,即broker-0下,只有ISR列表中剩余的follower即broker-1的partition-0将消息同步成功之后,生产者的这条消息才算成功发送至Kafka。

acks设置为0时,效率最高,但难以保证数据不丢失。
acks设置为1时,效率相对较高,安全性也相对较高,但依旧无法保证数据不丢失。根据3.2和3.3的描述,若follower尚未完全同步消息,leader突然挂掉,则可能衍生出数据安全问题。
acks设置为all时,安全性最高,但效率最低。

3.7 Offset

Topic的每个分区都是一个有序的、不可变的消息序列,新的消息不断追加进这个序列后,分区会给每条消息分配一个顺序ID,也就是Offset。
实际上,Offset也可分为Current Offset和Committed Offset以便于理解。
Current Offset由消费者维护,它代表消费者想要从哪条消息开始消费。
Committed Offset由broker维护,它代表实际已消费掉的消息序号ID。
消费者API内部实现了poll()方法,可以一次性从对应的分区拉取一至多条消息,举例:当消费者此时的Current Offset为10,那么消费者则会从第10条消息开始拉取,假设消费者拉取了5条消息,且对这5条消息进行Commit操作之后,broker的Committed Offset就会变成15,下次消费者就会从16开始拉取。
若消费者在消费的过程中出现故障,则未进行Commit的消息不会视为已消费,重启之后再次poll则会从已Commit的消息后一条进行消费。举例:消费者从第10条开始poll,拉取了5条,但期间出现问题,未能Commit,则下次poll还会从第10条开始。

3.8 消费者消息的分区消费策略

与生产者不一致的是,生产者无需限定数量,消费者需要根据group限定数量;生产者与分区没有实际的绑定关系,因为消息写入哪个分区是由broker根据策略分配的,而消费者与分区则有着密不可分的关系。均匀的分配消费者能够进一步提升Kafka的消费能力。
分区消费策略如下:

3.8.1 RangeAssignor策略

将同一个Topic的分区数量除以同一消费群里的消费者数量得出结果并分配至每个消费者。
举例A:同一消费群下的两个消费者C0和C1,都监听了t0和t1两个topic,每个topic都有p0至p3四个分区,则分配结果如下:
C0: t0p0, t0p1, t1p0, t1p1
C1: t0p2, t0p3, t1p2, t1p3
这种分配结果看似分配均匀,但分区数量如果不能被消费者数量整除,则分配结果会截然不同。
举例B:在A例的基础上,将每个topic的分区数量改为3,则分配结果如下:
C0: t0p0, t0p1, t1p0, t1p1
C1: t0p2, t1p2
如果使用这种策略,那么当监听的topic数量过多时,则可能出现部分消费者过载的风险。而我们实际想要的结果应当是每个消费者都能分配到3份分区。

3.8.2 RoundRobinAssignor策略

将同一消费群里的所有消费者及所有topic的所有分区按照字典排序,然后轮询分配给消费者。
以3.8.1中的B例继续为证。
举例B:分配结果如下:
C0:t0p0, t0p2, t1p1
C1:t0p1, t1p0, t1p2
分配顺序逻辑图如下:在这里插入图片描述

这种分配结果看似也很均匀,但若进一步调整,情况又会大不一样。
举例C:C0监听t0和t1两个Topic,C1监听t1一个Topic,且t0有两个分区,t1有四个分区,分配结果如下:
C0:t0p0, t0p1, t1p0, t1p2
C1:t1p1, t1p3
分配顺序图如下:在这里插入图片描述

这样的分配结果显然差强人意,因为理想的分配结果应当是每个消费者都能分配到3份分区。

3.8.3 StickyAssignor策略

StickyAssignor策略是Kafka从0.11x引入的,其分配策略的主要思想是将以上两种策略都纳入计算范围,但实际的计算逻辑却异常复杂,此处不再赘述,其分配原则主要有以下几点。

  1. 分配结果要尽量的均匀。
  2. 每一次的分配结果都要尽量与上一次的分配结果保持一致,减少变动。
  3. 当以上两点冲突时,优先第一点。

分配举例可参考网上的帖子:https://www.cnblogs.com/felixzh/p/11935693.html

StickyAssignor策略相对于另外两种策略的分配结果更加均匀,但其计算逻辑异常复杂,在实际运用中,出现突发事件,如某个消费者死机导致脱离了消费群,那么Kafka重新使用这个策略进行分配,则会非常占用资源。若当部分消费者正在消费消息,却突然要重新分配,那么这期间的分配将会更加损耗系统性能。

4. 拓展讨论

4.1 如何分配分区和副本,以及消费者如何部署

Topic分区和副本的数量直接影响到这个Topic消息的吞吐性能,因此需要正确的分配。分区的数量既要考虑到生产者发布消息的频率,又要考虑消费者消费消息的频率,而后者往往是重中之重。推荐以下思路:

  1. 对一个topic而言,分区数量*副本因子/broker节点数量,其结果尽量整除没有余数。
  2. 分区数量并非越多越好,分区数量影响到消费者的数量,消费者数量又会影响到应用服务的开发部署。
  3. 使用StickyAssignor策略,尽量保证消费者分配结果均匀,人工考虑重新分配结果减少变动的解决方案。
  4. 同一消费群里的消费者所监听的topic尽量相同,且每个topic尽量保持一致的分区数量和副本因子。
  5. 尽量减小消费者脱离群组而导致Kafka重新分配的运算消耗、以及避免新的分配结果不均匀。若要实现这样的效果,需要有以下两个保证。
    a: 需要保证群组里的消费者数量,比分区数量多。这样一来当某一个应用服务宕机而出现的消费者脱离群组,Kafka便能直接从多余的消费者直接选取一个引入分配,便可减小和原来的分配结果之间的差距,运算量和消耗将会大幅度降低,且新的分配结果依旧是均匀的。
    b: 需要保证每个消费者最后只会被分配到一个分区。若某个消费者被分配的分区数量大于一个,那么当新的消费者加入群组时,Kafka就会继续分配。(消费者数量过多,容易造成应用服务的消费者实例维护成本过高,该举措谨慎采取,原因参见4.2)在这里插入图片描述

4.2 关于Spring-Kafka的@KafkaListener注解

4.1中第5点的b证提到的“保证每个消费者最后只会被分配到一个分区”,其实现方案此处以Spring-Kafka为例。
根据Spring-Kafka的集成,可使用@KafkaListener注解,且只指定一个topic,该注解可认定为一个消费者,每个应用服务中可指定多个注解来监听多个topic。Spring-Kafka的消费者内部消费逻辑是单线程,即poll()方法是单线程的,尽管我们可以使用多线程处理poll到的多个消息,但是每poll到N条消息,在提交offset之前,这个消费者不会继续poll消息;也就是说,当一个消费者监听多个topic时,每次poll都只会从一个topic中拉取消息,若其它topic中的消息需要这个消费者继续拉取,则只能等待,这样就存在消费者过载、消息阻塞的风险。因此,每个消费者只监听一个topic,可以有效解决这个问题,但也会衍生新的问题,那就是每个应用服务的消费者实例会大大增加,系统维护消费者容器的成本则会大大增加。
以下图为例:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值