ZooKeeper在大型分布式系统中的应用之Kafka。

        Kafka主要用于实现低延迟的发送和收集大量的事件和日志数据—— 这些数据通常都是活跃的数据。所谓活跃数据,在互联网大型的Web网站应用中非常常见,通常是指网站的PV数和用户访问记录等。这些数据通常以日志的形式记录下来,然后由一个专门的系统来进行日志的收集与统计。

        Kafka是一个吞吐量极高的分布式消息系统,其整体设计是典型的分布与订阅模式系统。在Kafka集群中,没有“中心主节点”的概念,集群中所有的服务器都是对等的,因此,可以在不做任何配置更改的情况下实现服务器的添加与删除,同样,消息的生产者和消费者也能够做到随意重启和机器的上下线。Kafka服务器及消息生产者和消费者之间部署关系如下图所示。

       

术语介绍

        尽管Kafka是一个近似符合JMS规范的消息中间件实现,但是为了让读者能够更好的理解余下部分的内容,这里首先对Kafka中的一些术语进行简单地介绍。

  • 消息生产者,即Producer,是消息产生的源头,负责生成消息并发送到Kafka服务器上。
  • 消息消费者,即Consumer,是消息的使用方,负责消费Kafka服务器上的消息。
  • 主题,即Topic,由用户定义并配置在Kafka服务端,用于建立生产者和消费者之间的订阅关系:生产者发送消息到指定Topic下,消费者从这个Topic下消费消息。
  • 消息分区,即Partition,一个Topic下面会分为多个分区,例如“kafka-test”这个Topic可以分为10个分区,分别由两台服务器提供,那么通常可以配置为让每台服务器提供5个分区,假设服务器ID分别为0和1,则所有分区为0-0、0-1、0-2、0-3、0-4和1-0、1-1、1-2、1-3、1-4.消息分区机制和分区的数量与消费者的负载均衡机制有很大关系。
  • Broker,即Kafka的服务器,用于存储消息,在消息中间件中通常被称为Broker。
  • 消费者分组,即Group,用于归组同类消费者。在Kafka中,多个消费者可以共同消费一个Topic下的消息,每个消费者消费其中的部分消息,这些消费者就组成了一个分组,拥有同一个分组名称,通常也被称为消费者集群。
  • Offset,消息存储在Kafka的Broker上,消费者拉取消息数据的过程中需要知道消息在文件中的偏移量,这个偏移量就是所谓的Offset。

Broker注册

        Kafka是一个分布式的消息系统,这也体现在其Broker、Producer和Consumer的分布式部署上。虽然Broker是分布式部署并且相互之间是独立运行的,但还是许哟啊有一个注册系统能够将整个集群中的Broker服务器都管理起来。在Kafka的设计中,选择了使用ZooKeeper来进行所有Broker的管理。

        在ZooKeeper上会有一个专门用来进行Broker服务器列表记录的节点,下文中我们称之为“Broker节点”,其节点路径为/brokers/ids。

        每个Broker服务器在启动时,都会到ZooKeeper上进行注册,即到Broker节点下创建属于自己的节点,其节点路径为/broker/ids/[0...N]。

        从上面的节点路径中,我们可以看出,在Kafka中,我们使用一个全局唯一的数字来指代每一个Broker服务器,可以称其为“Broker ID”,不同的Broker必须使用不同的Broker ID进行注册,例如/broker/ids/1 和/boker/ids/2 分别代表了两个Broker服务器。创建完Broker节点后,每个Broker就会将自己的IP地址和端口等信息写入到该节点中去。

        请注意,Broker创建的节点是一个临时节点,也就是说,一旦这个Broker服务器宕机或是下线后,那么对应的Broker节点也就被删除了。因此我们可以通过ZooKeeper上Broker节点的变化情况来动态表征Broker服务器的可用性。

Topic注册

        在Kafka中,会将同一个Topic的消息分成多个分区并将其分布到多个Broker上,而这些分区信息以及与Broker的对应关系也都是由ZooKeeper维护的,由专门的节点来记录,其节点路径为/brokers/topics。下文呢种我们将这个节点称为“Topic节点”。Kafka中的每一个Topic,都会以/brokers/topics/[topic]的形式记录子啊这个节点下,例如/brokers/topics/login和/brokers/topics/search等。

        Broker服务器在启动后,会到对应的TOpic节点下注册自己的Broker ID,并写入针对该Topic的分区总数。例如,/brokers/topics/login/3→2这个节点表名Broker ID为3的一个Broker服务器,对于“login”这个Topic的消息,提供了2个分区进行消息存储。同样的,这个分区数节点也是一个临时节点。

生产者负载均衡

        在上面的内容中,我们讲解了Kafka是分布式部署Broker服务器的,会对同一个Topic的消息进行分区并将其分布到不同的Broker服务器上。因此,生产者需要将消息合理的发送到这些分布式的Broker上——这就面临一个问题:如何进行生产者的负载均衡。对于生产者的负载均衡,Kafka支持传统的四层负载均衡,同时也支持使用ZooKeeper方式来实现负载均衡,这里我们首先来看使用四层负载均衡的方案。

四层负载均衡

        四层负载均衡方案在设计上比较简单,一般就是更具生产者的IP地址和端口来为其确定一个相关联的Broker。通常一个生产者只会对应单个Broker,然后该生产者生成的所有消息都发送给这个Broker。从设计上,我们可以很容易发现这种方式的优缺点:好处是整体逻辑简单,不需要引入其他三方系统,同时每个生产者不需要同其他系统建立额外的TCP链接,只需要和Broker维护单个TCP链接即可。

        但这种方案的弊端也是显而易见的,事实上该方案无法做到真正的负载均衡。因为在系统实际运行过程中,每个生产者生成的消息量,以及每个Broker的消息存储量都是不一样的额,如果有些生产者产生的消息远多于其他生产者的话,那么会导致不同的Broker接收到的消息总数非常不均匀。另一方面,生产证也无法实时感知到Broker的新增与删除,因此,这种负载均衡方式无法做到动态的负载均衡。

使用ZooKeeper进行负载均衡

        在Kafka中,客户端使用了基于ZooKeeper的负载均衡策略来解决生产者的负载均衡问题。在前面内容中也已经提到,每当一个Broker启动时,会首先完成Broker注册过程,并注册一些诸如“有哪些可订阅的Topic”的元数据信息。生产者就能够通过这个节点的变化来动态的感知到Broker服务器列表的变更。在实现上,Kafka的生产者会对ZooKeeper上的“Broker的新增与减少”、“TOpic的新增与减少”和“Broker与Topic关联关系的变化”等事件注册Watcher监听,这样就可以实现一种动态的负载均衡机制了。此外,在这种模式下,还能够允许开发人员控制生产者根据一定的规则(例如根据消费者的消费行为)来进行数据分区,而不仅仅是随机算法而已——Kafka将这汇总特定的分区策略称为“语义分区”。显然,ZooKeeper在整个生产者负载均衡的过程中扮演了非常重要的角色,通过ZooKeeper的Watcher通知嫩巩固让生产者动态的获取Broker和Topic的变化情况。

消费者负载均衡

        与生产类似,Kafka中的消费者同样许哟啊进行负载均衡来实现多个消费者合理的从对应的Brooker服务器上接收消息。Kafka有消费者分组的概念,每个消费者分组中都包含了若干个消费者,每一条消息都只会发送给分组中的一个消费者,不同的消费者分组消费自己特定Topic下面的消息,互不干扰,也不需要互相进行协调。因此消费者的负载均衡也可以看作是同一个消费者分组内部的消息消费策略。

消息分区与消费者关系

        对于每个消费者分组,Kafka都会为其分配一个全局唯一的Group ID,同一个消费者分组内部的所有消费者都共享该ID。同时,Kafka也会为每个消费者分配一个Consumer ID,通常采用“Hostname:UUID”的形式来表示。在Kafka的设计中,规定了每个消息分区有且只能同时有一个消费者进行消息的消费,因此,需要在ZooKeeper上记录下消息分区与消费者之间的对应关系。每个消费者一旦确定了对一个消息分区的消费权利,那么需要将其Consumer ID写入到对应消息分区的临时节点上,例如/consumers/[group_id]/[owners]/[topic]/[broker_id-partition_id],其中“[broker_id-partition_id]”就是一个消息分区的标识,节点内容就是消费该分区上消息的消费者的eConsumer ID。

消息消费进度Offset记录

        在消费者对指定消息分区进行消息消费的过程中,需要定时的将分区消息的消费进度,即Offset记录到ZooKeeper上去,以便在该消费者进行重启或是其他消费者重新接管该消息分区的消息消费后,能够从之前的进度开始继续进行消息的消费。Offset在ZooKeeper上的记录由一个专门的节点负责,其节点路径为/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id],其节点内容就是Offset值。

消费者注册

        下面我们再来看看消费者服务器在初始化启动时加入消费者分组的过程。

  • 注册到消费者分组。
  • 对消费者分组中消费者的变化注册监听。
  • 对Broker服务器的变化注册监听。
  • 进行消费者负载均衡。

负载均衡

        Kafka借助ZooKeeper上记录的Broker和消费者信息,采用了一套特殊的消费者负载均衡算法。由于该算法和ZooKeeper本身关系并不是特别大,因此这里只是结合官方文档来对该算法进行简单的陈述。

        我们将一个消费者分组的每个消费者记为C1,C2,...,Ci,...,CG,那么对于一个消费者Ci,其对应的消息分区分配策略如下。

  1. 设置PT为指定Topic所有的消息分区。
  2. 设置CG为同一个消费者分组中的所有消费者。
  3. 对PT进行排序,使分布在同一个Broker服务器上的分区尽量靠在一起。
  4. 对CG进行排序。
  5. 设置i为Ci在CG中位置的索引值,同时设置N=size(PT)/size(CG)。
  6. 将编号为i*N~(i+1)*N-1的消息分区分配给消费者Ci。
  7. 重新更新ZooKeeper上消息分区与消费者Ci的关系。

小结

        Kafka从设计之初就是一个大规模的分布式消息中间件,其服务端存在多个Broker,同时为了达到负载均衡,将每个Topic的消息分成了多个分区,并分布在不同的Broker上,多个生产者和消费者都能够同时发送和接收消息。Kafka使用ZooKeeper作为其分布式协调框架,很好的将消息生产、消息存储和消息消费的过程有机的结合起来。同时借助ZooKeeper,Kafka能够在保持包括生产者、消费者和Broker在内的所有组件无状态的情况下,建立其生产者和消费者之间的订阅关系,并实现了生产者和消费者的负载均衡。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值