写在前面
Kafka在很多业务场景中被使用,比如进行业务模块间的解耦及消息传递,进行系统的削峰降压等。现在的很多系统都部署在分布式的环境之下,比较著名的有Hadoop项目,其中的很多组件,例如HDFS、Hbase、MapReduce等依赖于ZooKeeper提供分布式的协调服务,Kafka作为Hadoop生态系统中的一员,也依赖于ZooKeeper为其提供分布式的协调服务。
本文简单介绍一下Kafka的技术原理、ZooKeeper的数据结构 & ZooKeeper如何为Kafka提供分布式的协调服务,文章篇幅有限,很多关于ZooKeeper的技术原理没有涉及到,比如ZooKeeper如何提供高可靠的服务、如何在leader结点宕机时保证服务的稳定性 & 如何保证数据的一致性等等,有兴趣可以看看相关的文章。
ZooKeeper & Kafka
Kafka
kafka是一个分布式的消息流处理平台,这意味着它有三个关键的特定
- 发布和订阅到kafka中的消息,就像消息队列一样
- 以一种可容错、持久化的方式存储kafka中的消息记录
- 在消息发布到kafka时,可以马上进行消费
基本概念
- 作为一个分布式平台,kafka可以运行在一个或者多个机器之中
- 以主题(topic)对发布到kafka中的消息进行分类
- 每一个消息(record)包含key、value、timestamp
kafka有5大核心的API
- Producer API:用于发布消息到Kafka的指定topic中
- Consumer API:用于消费Kafka中指定的topic中的消息
- Streams API:作为流处理器,对于一个或多个输入的topic中的消息,处理转化后输出到指定的一个或多个topic中
- Connector API:作为连接器,可用于连接到一个关系型数据库之中,实时将变化的数据同步到连接的数据库表格中
- Admin API:用于管理Kafka集群中的机器、topic和其它对象
Topic
在一个Kafka集群之中,消息以主题(topic)进行分类
- topic是一个抽象的分布式概念,一个topic由一个或多个分区(partition)组成,同一个topic的partition一般分散在集群的多个机器之中
- producer发布消息到指定的topic之中
- consumer订阅并消费指定topic中的消息
partition
partition是一个有序的、不可变的队列,每个发布的消息会以追加的形式append到队列末尾之中
- 一个partition只能由一个消息组中的一个消费者消费,当然,可以同时由其它的消费组消费
- 存储在partition中的record持久化在内存之中,一般会在指定的过期时间之后丢弃
- 容错策略:可以指定partition的备份数量
- 每个存储在partition中的record有一个offset作为唯一标示,consumer持有offset指定消费分区中的record
partition的读写优化策略
kafka的消息是存储在硬盘之中的,但是kafka作为高吞吐率的分布式平台,有如下的读写优化策略
写优化
- 顺序写入:Kafka使用顺序I/O,降低了读写磁盘时的寻址时间
- 内存映射文件(Memory Mapped Files):Kafka的数据并不是实时的写入硬盘 ,它充分利用了现代操作系统分页存储来利用内存提高I/O效率,将内存分页映射到硬盘内存之中,在适当的时候将数据刷新到硬盘之中
读优化
- 存储在服务器中的partition被分割成多个小文件进行存储,每个文件以起始record的offset命名,可以以二分搜索的方式读取指定分区的record
- 批量压缩:将partition中的record进行批量压缩,提高网络传输速度
生产者
producer发布record到指定topic的特定partition之中
消费者
- 每个消费者都属于某一个消费者组(consumer group)之中
- 一个topic中的partition只能被一个消费者组中的一个消费者消费,但是一个同时被多个消费者组消费
- 消费者持有record的offset,可以顺序地消费partition中的record,也可以选择性地消费指定offset的record
Kafka与Redis的区别
- Kafka将数据写入到硬盘之中,保证了数据的高可靠性,同时,允许一个partition可以有多个备份,保证了服务的高可靠性;Redis将数据存储在内存之中,虽然可以定时地持久化到硬盘中,但仍然无法保证数据不会出错
- Kafka支持以topic对消息进行分类,并将topic中的消息以partition的形式存储在多台服务器之中;Redis没有这样子的概念
- Kafka一般用于高可靠性的业务回调;Redis一般用于高响应的业务系统之中
ZooKeeper
ZooKeeper 是一个开源的分布式协调服务(只要半数以上节点存活,ZooKeeper 就能正常服务)
数据结构
ZooKeeper提供的名称空间与标准文件系统的名称空间非常相似,以树结构进行组织,树中的每一个结点成为Znode
- 名称是由斜杠(/)分隔的一系列路径元素,ZooKeeper命名空间中的每个节点都由路径进行唯一标识
Znode
在每个Znode中,包含数据(data)、访问控制权限(ACL)、子节点引用(child)和元数据信息(stat)
- data:Znode存储的数据信息
- ACL:记录Znode的访问权限,即哪些人或哪些IP可以访问本节点
- stat:包含Znode的各种元数据,比如事务ID、版本号、时间戳、大小等等
- child:当前节点的子节点引用,类似于二叉树的左孩子右孩子
Watch
每个Znode可以设置Watch,可以理解成是注册在特定Znode上的触发器。当这个Znode发生改变,也就是调用了create,delete,setData方法的时候,将会触发Znode上注册的对应事件,注册Watch的客户端会接收到异步通知
Zookeeper在Kafka中的作用
kafaka集群的 broker,和 Consumer 都需要连接 Zookeeper。
Producer 直接连接 Broker
Broker注册
Broker是分布式部署并且相互独立,通过注册在Zookeeper之中,交由Zookeeper进行协调管理
- 在Zookeeper上会有一个专门用来进行Broker服务器列表记录的节点:/brokers/ids
- 每个Broker在启动时,都会到Zookeeper上进行注册,即到/brokers/ids下创建属于自己的节点,如/brokers/ids/[0...N]
- Kafka使用了全局唯一的数字(Broker ID)来指代每个Broker服务器
- 创建完节点后,每个Broker就会将自己的IP地址和端口信息记录到该节点中去
- Broker创建的节点类型是临时节点,一旦Broker宕机,则对应的临时节点也会被自动删除
Topic注册
在Kafka中,同一个Topic的消息会被分成多个分区并将其分布在多个Broker上
- 分区信息及与Broker的对应关系也都是由Zookeeper在维护,由专门的节点来记录:/borkers/topics
- Kafka中每个Topic都会以/brokers/topics/[topic]的形式被记录,如/brokers/topics/login和/brokers/topics/search等
- Broker服务器启动后,会到对应Topic节点(/brokers/topics)上注册自己的Broker ID并写入针对该Topic的分区总数,如/brokers/topics/login/3->2,这个节点表示Broker ID为3的一个Broker服务器,对于login这个Topic的消息,提供了2个分区进行消息存储
- 这个分区节点也是临时节点
生产者负载均衡
同一个Topic消息会被分区并将其分布在多个Broker上,因此,生产者需要将消息合理地发送到这些分布式的Broker上
- 使用Zookeeper进行负载均衡,由于每个Broker启动时,都会完成Broker注册过程,生产者会通过该节点的变化来动态地感知到Broker服务器列表的变更,这样就可以实现动态的负载均衡机制
消费者负载均衡
每个消费者分组包含若干消费者,每条消息都只会发送给分组中的一个消费者,不同的消费者分组消费自己特定的Topic下面的消息,互不干扰
- 与生产者类似,通过Broker注册节点的变化来动态地感知到Broker服务器列表的变更,实现动态的负载均衡机制
分区 与 消费者 的关系
在Kafka中,规定了每个消息分区 只能被同组的一个消费者进行消费,因此,需要在 Zookeeper 上记录 消息分区 与 Consumer 之间的关系
- 每个消费者一旦确定了对一个消息分区的消费权力,需要将其Consumer ID 写入到 Zookeeper 对应消息分区的临时节点上,例如:/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]
- 其中,[broker_id-partition_id]就是一个 消息分区 的标识,节点内容就是该 消息分区 上 消费者的Consumer ID
消息 消费进度Offset 记录
在消费者对指定消息分区进行消息消费的过程中,需要定时地将分区消息的消费进度Offset记录到Zookeeper上,以便在该消费者进行重启或者其他消费者重新接管该消息分区的消息消费后,能够从之前的进度开始继续进行消息消费
- Offset在Zookeeper中由一个专门节点进行记录,其节点路径为:/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]
- 节点内容就是Offset的值
消费者注册
注册到消费者分组。每个消费者服务器启动时,都会到Zookeeper的指定节点下创建一个属于自己的消费者节点,例如/consumers/[group_id]/ids/[consumer_id],完成节点创建后,消费者就会将自己订阅的Topic信息写入该临时节点
- 对消费者分组中的消费者的变化注册监听:每个 消费者都需要关注所属消费者分组中其他消费者服务器的变化情况,即对/consumers/[group_id]/ids节点注册子节点变化的Watcher监听,一旦发现消费者新增或减少,就触发消费者的负载均衡
- 对Broker服务器变化注册监听:消费者需要对/broker/ids/[0-N]中的节点进行监听,如果发现Broker服务器列表发生变化,那么就根据具体情况来决定是否需要进行消费者负载均衡
- 进行消费者负载均衡:为了让同一个Topic下不同分区的消息尽量均衡地被多个消费者消费而进行消费者与消息分区分配的过程,通常,对于一个消费者分组,如果组内的消费者服务器发生变更或Broker服务器发生变更,会发出消费者负载均衡
参考文章
官方介绍
Zookeeper在kafka中的作用