浅谈Kafka特性与架构

Kafka特性

kafka做为一种消息中间件,有如下特性:java

  • 高吞吐量:吞吐量高达数十万
  • 高并发:支持数千个客户端同时读写
  • 低延迟:延迟最低只有几毫秒
  • 消息持久性和可靠性:消息被持久化到本地磁盘,同时支持数据备份
  • 集群容错性:容许n-1个节点失败(n为副本个数)
  • 可扩展性:支持集群动态扩展

应用场景

根据Kafka的特性,有如下应用场景:算法

  • 消息中间件:kafka自己做为标准的消息中间件,能够用于producer和consumer之间的异步消息通讯
  • 日志:出于kafka的高吞吐量特性,能够进行高效地日志收集
  • 数据收集:出于高吞吐量和高并发特性,可使用kafka记录用户/系统的一些实时数据

主要名词

  • Broker:每一台Kafka服务器就叫作一个Broker,支持水平扩展,一个集群中一般有多台Broker,各个Broker地位一致,不存在主从关系
  • Coordinator:集群的协调者,kafka会将负载最小的broker指定为Coordinator
  • Topic:全部消息都有本身的所属分类,这个分类就叫作Topic。一个Topic下的消息能够保存在多个Broker上(对于Producer和Consumer是无感知的)
  • Producer:产生消息的主体叫作Producer,负责发布消息到指定Topic中
  • Consumer:消费对象的主体叫作Consumer,负责消费指定Topic中的消息
  • ConsumerGroup(CG):每个Consumer均属于一个特定CG,一个Topic能够对应多个CG,Topic的消息会发送到全部CG,可是CG能够选择发送给全部Consumer仍是指定的Consumer,经过这种方式能够方便的实现单播和广播。同时,同一个CG下的Consumer能够实现负载均衡
  • Partition:存放数据的具体物理实体,每个Topic会分为多个Partition。每个Partition对应一个文件夹,在文件夹下存放数据和索引文件。每个Partition中的消息是有序的,可是不一样Partition的数据不能肯定顺序
  • Replication:Partition的备份,一个Partition会有多个Replication,存放在不一样的Broker上
  • Segment:指每个数据文件,一个Partition对应多个Segment,每个Segment会有一个索引文件与之对应
  • Offset:指消息的序列号,是连续递增的,Partition中的每个消息都会有本身的Offset,用于惟一标识一条消息。由于是有序的,因此能够根据Offset快速定位一个数据文件

基本架构

kafka是一个自然支持分布式架构的发布订阅模式的rpc通讯框架,kafka集群为典型的去中心化的设计,主体设计以下:api

生产者向Kafka集群提供数据,消费者从Kafka集群拉取数据,Kafka集群的调度由Zookeeper负责

Zookeeper

Kafka集群的元数据保存在Zookeeper中,除此以外不存储任何消息数据。每个Broker都须要在Zookeeper上注册并不断在上面更新本身的元数据(Topic和Partition信息),Zookeeper会使用这些数据信息来实现动态的集群扩容数组

Producer和Consumer都会在Zookeeper上注册监听器(Watcher),用于在Zookeeper发生变化时做出响应的调整。同时,Consumer还会向Zookeeper中注册本身消费的Partition列表,用于发现Broker并与Partition创建socket链接缓存

核心组件

Partition

Kafka中的Topic是以Partition的形式存放的,一个Topic会被拆分为多个Partition,存放在多台服务器上。Producer在生产数据时会根据必定的规则将数据写入指定Topic下的Partition中服务器

能够设置每个Topic的Partition数量,可是须要注意的是,一个Partition只能供一个Consumer消费,若是Partition过少,就可能会有Consumer消费不到数据。另外,建议partition的数量也须要大于集群中Broker的数量,这样可让Partition Leader尽可能均匀地分布在各个Broker中。同时也须要注意,拆分的Partiton越多,也就意味着须要更多的空间网络

一般一个Partition须要有数个副本(Replication),Kafka容许用户设置一份数据的备份个数,副本会存储在不一样的Broker上。在全部的副本中(包括本身),会存在一个Partition Leader用于进行读写,Leader的选举调度等操做由Zookeeper来完成数据结构

Producer

Producer直接将消息发送到Broker的Partition Leader上,不须要通过代理中转等操做,由于在设计时,Kafka集群中的每个Broker均可以单独响应Producer的操做,并返回Topic的一些信息(存活的机器/Leader位置/...)架构

Producer客户端负责采用指定的负载均衡算法,管理消息会被推送到哪些Partition上。同时Producer能够将消息在内存中累计到必定数量时,做为一个Batch进行发送,可以有效减小IO次数,进而提升效率。具体的Batch参数能够手动设置,能够是累计的数量大小/时间间隔等并发

Producer能够异步地向Kafka发送数据,在发送后会收到一个Futrue响应,包含offset值等信息。能够经过指定acks参数来控制Producer要求收到的确认消息个数

  • acks参数为n时:只有当n个partition副本收到消息后,producer才会收到broker的确认
  • acks参数为-1时:producer会在全部partition副本收到消息后获得broker的确认
  • acks参数为0时:producer不会等待broker的响应,能够获得最大的吞吐量,可是可能会致使数据丢失

Consumer

Kafka中,读取消息的offset值由Consumer进行维护,所以consumer能够自由选取读取消息的方式。同时,无论消息有没有被消费,数据都会在kafka中保存一段时间

Kafka提供了两种consumer api,分别是high-level api和sample api。Sample api只维持了和单一Broker的链接,同时是无状态的,每次请求都须要指定offset值,因此也更为灵活

High-Level api封装了对集群中broker的访问,能够透明的访问一个topic,同时也维持了已消费消息的状态,每次消费的都是下一个消息。High-Level api还支持以组(CG)的形式消费消息,消息会被发送给全部的CG,CG内部会选择按顺序发送给全部Consumer或是指定的Consumer

核心机制

消息压缩

Kafka能够以集合(batch)形式发送数据,在此基础上,kafka能够对batch进行压缩。在producer端进行压缩后,在consumer进行解压,减小了传输所需的数据量,减轻对网络的压力。kafka在消息头部增长了一个字节用于描述压缩属性,这个字节后两位表示压缩采用的编码,若是后两位为0,表示消息未被压缩

消息可靠性

最理想的状况是消息发送成功,而且只发送了一次,这种状况叫作exactly-once,可是不可避免的会发生消息发送失败以及消息重复发送的状况

为了解决这类问题,在producer端,当一个消息被发送后,producer会等待broker发送响应,收到响应后producer会确认消息已经被正确发送给kafka,不然就会从新发送

在consumer端,由于broker记录了partition中的offset值,这个值指向consumer下一个消费的消息,若是consumer收到消息可是消费失败,broker能够根据offset值来找到上一个消息,同时consumer还能够控制offset值,来对消息进行任意处理

备份机制

(在“核心组件-Partition”中已经对此部分作了叙述)

消息消费策略

消费策略分类

固定分区消费

consumer在进行消息消费时,能够指定消息某分区的消息

Rebalance分区消费

通常地,一个topic下会有多个partition,而一个partition只能被一个CG中的consumer消费,能够经过指定rebalance策略,来采用不一样的消费方式。Rebalance策略有两种,范围分区(Range)和轮询分区(RoundRobin),范围分区策略,即对topic下的partition进行排序,将partition数量除以CG下的consumer数量,从而得出每个consumer消费哪几个分区

轮询分区策略则是将partition按照hashcode进行排序,而后经过分区取模来给consumer分配partition

Rebalance的触发时机

当如下三种状况发生时,会触发rebalance操做,从新指定分区:

  • CG内部加入了新的consumer
  • consumer离开CG
  • topic新增partition

Rebalance的执行过程

rebalance的执行由CG Leader来完成,并负责在执行结束后将执行结果经过broker集群中的coordinator广播到CG。当CG的第一个consumer启动后,这个consumer会和kafka肯定组内的coordinator,以后CG内的全部成员都会和该coordinator进行通讯

CG Leader的选举有两个阶段,Join GroupSynchronizing Group State

  1. Join Group阶段,全部成员都会向coordinator发送JoinGroup请求,当全部consumer都发送请求后, coordinator会选择一个consumer担任leader,并把CG的信息发送给该leader
  2. Synchronizing Group State阶段,全部consumer都会向coordinator发送SynchronizingGroupState请求,而leader则将分区方案发送给coordinator,coordinator会在接受到分区方案后,将分区结果返回给全部consumer,这样就完成分区方案的同步

高效性设计

消息持久化

消息的持久化并不只仅是出于数据备份的须要,一个事实是,线性读写的时间远远高于随机读写,对磁盘的线性读所消耗的时间在有些状况下能够比内存的随机访问更快,因此现代不少操做系统会把空闲的内存用做磁盘缓存,尽管会在内存回收时带来性能损耗,可是在读写上带来的效率提高是显著的

基于这样的事实,利用文件系统依靠页缓存来维护数据,会比维护一个内存缓存更好,由于采用了更为紧凑的数据结构。不一样于维护尽量多的内存缓存,若是咱们将数据写入到一个持久化日志中,不调用刷新程序,这意味着数据将被传输到内核中并在稍后被刷新,咱们也能够经过配置来控制数据在何时刷新到物理磁盘上

常数时间的保证

kafka中持久化消息队列采用对文件的读写来实现,相似日志的形式。尽管这种操做不支持丰富的语义,可是能够很高效的进行并行操做,而且全部的操做都是常数时间,最终系统的性能和数据大小彻底无关,能够充分利用硬盘来进行高效的消息服务

字节拷贝

为了解决字节拷贝的问题,kafka采用“标准字节消息”这种消息格式,这种格式在producer、consumer和broker间共享,kafka的日志文件都是按“标准字节消息”这种格式写入磁盘中。unix系统为了提升页面缓存和socket之间的数据传递效率,使用了“零拷贝”机制,即sendfile system call 系统调用,java中也提供了访问这个系统调用的接口

为了解释为何这种方式能解决字节拷贝带来的性能损耗,咱们先来描述将数据从文件发送到socket的通常步骤:

  1. os将数据从磁盘读到内核空间的页缓存中
  2. 应用将数据从内核空间读到用户空间的页缓存中
  3. 应用将数据写回内核空间的socket缓存中
  4. os将数据从socket缓存写到网卡缓存中
  5. 数据经网络发出

咱们能够发现这个过程至少涉及4次字节拷贝,2次系统调用,2次内核态到用户态的切换,而若是咱们可以直接将数据写入socket缓存中,就能减小不少没必要要的切换。若是使用了sendfile的方式,数据能够直接由内核页缓存直接拷贝到内核socket缓存中,不须要进行额外的系统状态切换。经过这种方式,即便下游有不少consumer,也不会对集群服务形成压力

想更详细了解零拷贝机制的可见个人另外一篇文章:浅谈零拷贝机制

频繁小IO

频繁的小io能够经过一次性发送一个消息集合,而不是只发送一条消息来解决,消息在服务器以消息块的形式添加到日志中。同时consumer在查询时也会一次查询大量的线性数据块。消息集合(Message Set)将一个字节数组或文件进行打包,同时能够有选择地进行反序列化

相关文章
相关标签/搜索