消息中间件 Kafka

假设一种case,一个任务耗时高达数十秒,使用同步方式调用,一言不合便是超时,这可如何是好呢?

首相想到的,当然是异步化。耗时巨大的任务,使用同步调用方式,比如HTTP或者RPC,超时是很难避免的了。一个更好的做法便是将任务放入一个队列,同时给这个任务赋予一个唯一标识符,根据这个唯一标识符去获取任务的结果。队列有许多种,比如著名的Kafka,Nsq,RabbitMq等等。不同的队列有着不一样的适用场景。

笔者今天来谈谈对Kafka这一消息中间件的理解。

Kafka可以说是当前最为完善,使用最广泛的消息中间件。Kafka底层使用Scala语言编写,运行时需要JVM环境支撑。Kafka通常需要与Zookeeper一同使用,Kafka集群信息存储于Zookeeper中,每当有新的实例加入或者旧的实例退出时,都会在Zookeeper中进行实例信息的更改。Kafka 0.9之前的版本还会将offset存储于Zookeeper中,0.9版本及之后,offset不再存储于Zookeeper中,而改成存储在broker实例中。关于Kafka的offset将会在下文中阐述,这也是Kafka最为重要的一个概念。

说说Kafka最基本的几个概念。

Producer(生产者)

消息队列的生产者端,也就是发消息的。生产者发送消息的时候,会指定发送到哪个topic。

Consumer(消费者)

消息队列的消费者端。消费者会订阅某个topic,它会接收到来自这个topic的消息。

Consumer Group(消费者组)

字面意思,消费者的组。对于同一个消费者组,组内会有多个消费者进程,一个消费者对应一个消费者进程。对于归属于同一组的消费者,此消费者组订阅了一个topic,那么,来自这个topic的消息只会被消费者组中的一个消费者进程消费,如图Figure1所示。

消息中间件 Kafka

Figure1

对于两个不同的消费者组,这两个消费者组订阅了同一个topic,那么,来自这个topic的消息对于这两个消费者组是广播的,如图Figure1所示。

Topic(主题)

上文中已提到。不同的topic之间是相互独立开的,不同的topic里面存放着不同的消息。通常一个Kafka队列会有多个不同的业务方接入,不同的业务方使用不同的topic相互之间完全隔离。

Partition(分区)

对于一个topic,里面有一个或者多个分区。生产者发送的消息进入到topic里面之后,会均匀的分布在不同的分区之中,如图Figure2所示。

消息中间件 Kafka

Figure2

对于同一个分区内部,消息是保持有序的,但是在有多个分区的情况下,topic整体上消息无序。因此,如果有什么业务场景要求消息保持绝对有序,一定要将分区数量设置为1。对于同一个消费者组内部,一个消费者进程可以消费一个或者多个分区的消息。如果消费者进程数量小于分区数量,那么一个进程消费多个分区;如果消费者进程数量大于分区数量,那么就会有进程被闲置,因为一个分区只能被一个消费者消费。因此,通常设置同一个消费者组内,消费者数量等于分区数量,此时能够达到最高吞吐量。

Broker(实例,也就是机器)

Kafka通常由集群构成,集群中的一台机器,也就是一台实例,就是一个broker。

Offset(偏移量,用于记录当前消费位置)

笔者认为,这是kafka中最为关键的一个概念。Kafka队列的原理是,生产者写入的消息会保存到本地磁盘文件,也就是说,生产者发送的所有的消息都会被保存下来。消费者消费消息时,消息不是真的被消费了,而是,消费一条消息之后,offset向后偏移一位,指向下一条消息的index,如图Figure3所示。

消息中间件 Kafka

Figure3

在Figure3中,生产者从左侧写入消息,消费者的offset从右侧开始消费偏移。图中所示的是一个partition内部的情况,对于一个完整的topic,每个partition都会为每一个消费者组维持着一个offset值。什么意思呢?对于一个消费者组,它订阅了一个topic,也就是说,它正在消费这个topic中的所有partition,每个partition中都为这个消费者组,维护着一份offset,来表明该消费者组在这个partition中消费到了什么位置。当这个topic有多个消费者组订阅时,partition就会为每一个消费者组维护一份offset,因为不同的消费者组消费到的位置不一样。Offset不是永远只会往后移动的,他是可以自定义调整位置的。只要生产者成功写入了消息,消息就会被Kafka保存下来。如果消费者因为一些原因没能消费成功,那么可以通过修改偏移量offset来重新进行一次消费。在一些精确度要求很高的场景,这种特性十分适用,比如计费,点钞票的时候因为一些原因点漏了可就不好了,Kafka存储了所有消息,就算出现了异常,通过修改offset重来一次就好了。

那么,有个问题,对于同一个消费者组内部,每个消费者进程,是怎样被分配给topic里面的partition的呢?

分配策略有很多,笔者在这里说说Kafka本身自带的默认的两种策略,他们是RangeRoundRobin

Range策略

该策略十分简单。假设现在有1~10合计10个分区,有1~3合计三个消费者进程。此时我们用 10 除以 3,得到3余1。对于三个消费者进程,我们先分别给3个消费者进程分配3个分区。此时还有一个分区,我们将其分配给第一个消费者。于是乎,分区分配情况就是【(1, 2, 3, 4), (5, 6, 7), (8, 9, 10)】。第一个消费者进程分陪了1~4号分区,第二个消费者进程分配了5~7号分区,第三个消费者进程分配了8~10号分区。

再举一个例子,假设现在有1~11合计11个分区,有1~3合计三个消费者进程。那么11除以3等于3余2。先平均分配,此时剩余2个分区,我们再将这两个分区分配给前面的两个消费者进程,也就是【(1,2,3,4),(5,6,7,8),(9,10,11)】。

RoundRobin策略

该策略会无视Kafka的topic,它会将topic+partition视为一个整体,随后计算其hashcode,根据计算出来的hashcode值,对所有的topic-partition进行排序。随后,按照RoundRobin原则,将这些topic-partition均分给所有的消费者进程,如图Figure4所示。

消息中间件 Kafka

Figure4

在只订阅一个topic的情况下,上述两种策略其实没有特别大的区别。然而在订阅了多个topic的情况下,RoundRobin策略将会优于Range策略。Figure5所示为Range策略的一个bad case。

消息中间件 Kafka

Figure5

我们按照Range策略进行了分区分配之后,我们发现,consumer1消费了6个分区,consumer2消费了5个分区,consumer3只消费了三个分区。那么,这种分配就严重不均衡了。但如果是使用了RoundRobin策略,情况就会好的多,因为它将topic-partition视为了一个整体,不会因为topic的缘故导致分配不均匀,如图Figure6所示。

消息中间件 Kafka

Figure6

在RoundRobin策略下,consumer1和consumer2各消费了4个partition,consumer3消费了3个partition,分配情况优于Range策略。

那么当有消费者进程退出或者启动的时候,会发生什么呢?这种情况下,原先的分配就被打乱了吧?

是的,确实被打乱了,此时就会发生重平衡(rebalance)。重平衡发生于消费者进程数量发生变化的时候,为了保持分区分配的合理性,需重新进行一次分区分配。重平衡进行的时候,消费者暂停消费,不过时间不会太长,有可能导致短暂的服务不可用。如果监测到一个消费者服务频繁地发生重平衡,应当检查服务是否存在存在进程退出的case。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值