目录

Kafka初识之架构之美

Kafka分布式事件流处理平台是一个开源的分布式事件流平台,被数千家公司用于高性能数组管道、流分析、数据集成和任务关键型应用程序。

Kafka是什么?
Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.

Kafka的使用场景

  • 消息系统(Messaging)

  • 网站活动跟踪(Website Activity Tracking)

  • 数据指标(Metrics)

  • 日志聚合(Log Aggregation)

  • 流处理(Stream Processing)

  • 事件驱动(Event Sourcing)

  • 日志存储(Commit Log)

以上信息源自官网, 详见: Kafka Use Cases

警告
本文对Kafka的分析均源自分布式消息系统的场景下

Kafka基本术语介绍

  • Broker

    一个单独的Kafka server就是一个Broker,kafka集群由多个Broker组成

  • Producer

    生产者,主要工作是往Broker发送(push)消息

  • Consumer

    消费者,主要工作是从Broker拉取(pull)消息

  • Topic

    主题,存储消息的逻辑概念。可以理解为rabbitMQ中的queue

  • Partition

    分区,每个Topic可以划分成多个分区

  • Log

    分区上存储数据的日志文件,生产者将消息写入分区时,实际上就是写入对应的Log

  • Segment

    段,Partition里的log文件会划分成多个段

  • Consumer Group

    Kafka中多个Consumer组成一个Consumer Group,一个Consumer必须只能属于一个Consumer Group

  • Replica

    副本,Kafka实现高可用的机制,每个Topic至少有一个副本

Kafka架构拓扑图

/kafka-introduction/kafka-architecture-diagram.jpg
Kafka Architecture-Diagram

Kafka架构初识

Kafka作为分布式消息中间件,可以很好的替代传统的消息系统。与大多数消息系统相比,具有更好的吞吐量、内置分区、复制和容错能力,这使它在大规模数据应用场景有着明显的性能优势。

最简单的消息系统一般都会包含:生产者producer、消费者consumer、队列queue。如下图所示,仅存一个队列的情况下,生产者先向队列里投递消息,然后消费者再从队列里消费消息。

/kafka-introduction/mq-queue.jpg
MQ Queue

先不谈Kafka的架构如何,单就上图最简单的场景,如果要提高效率,该怎么做?

在解答这个问题之前,先考虑一下以上mq模型存在哪些缺点:

  • 随着消息量增大,当queue承载的数据量过大时,影响读写性能,同时queue服务也可能宕机
  • 当有多个消费者同时消费同一队列里的数据时,需要保证消息分配正确及维护消费位移,而这个过程也是非常耗性能的
  • 当生产者发送消息的速度比消费者消费消息的速度快时,queue服务一直向消费者push消息,消费者可能承受不了压力而宕机

要解决以上问题,通过数据分块、多线程的方式可以缓解,但治标不治本。那么来看看Kafka是如何解决的吧。

Topic & Partition & Segment

顾名思义,主题。Kafka存储消息的逻辑概念,可以简单理解为上面所说的queue。生产者负责向Topicsend消息,消费者负责从Topicpull消息然后处理。

为了解决一个Topic里的数据文件过大导致的读写性能问题,Kafka将其划分为多个Partition。如下图所示:

/kafka-introduction/topic-partition.jpg
Topic & Partition

生产者发送消息的时候会按照策略,将消息发送至不同的Partition,发往每个Partiton里的消息会在分区内对应一个偏移量offset且均从0开始依次递增。Kafka保证一个Partiton内的消息是有序的,但无法保证一个Topic内数据的顺序性。如下图所示:

/kafka-introduction/partition.jpg
Partition

虽然将Topic划分多个Partition可以避免数据过于集中导致的问题,但当Partition中数据过大的时候还是会影响读写性能。因此Kafka再将Partition在物理层面下,通过文件大小、时间等策略细分多段segment,当该段文件大小或时间满足要求,则生成下一段文件数据,每段下均有其对应的索引文件来加速查询。而数据写入的方式则是顺序磁盘IO及文件追加写的形式。

Pull vs Push

首先考虑的一个问题,消费者是应该从Broker那里获取数据,还是Broker应该将数据推给消费者

  • push: 当生产者发送消息的速度比消费者消费消息的速度快时,Broker服务一直向消费者push消息,消费者可能承受不了压力而宕机。但这样的好处是,Broker不容易积压消息。
  • pull: 当生产者发送消息的速度比消费者消费消息的速度快时,消费者只是消费落后,后面可能赶上。消息积压在Broker

Replica机制

在分布式系统中,高可用是一个避不开的问题,而Kafka则是通过副本机制实现高可用。

创建Topic时可以指定副本数(至少一个副本)。当创建一个Topic,指定3个分区和3个副本。这样每个分区都有3个副本,Kafka根据一定策略,选出每个分区里的leaderfollower,然后尽力确保每个分区的leader落在不同的Broker上。当有Broker宕机的时候,就会触发分区副本选举机制,选举出新的leader。如下图所示:

/kafka-introduction/broker.jpg
Broker

ISR & HW & LEO

AR、ISR、OSR是什么?

AR(Assigned Replicas): 分区中的所有副本

ISR(In-Sync Replicas): 所有与leader副本保持一定程度同步的副本

OSR(Out-of-Sync Replicas): 与leader副本同步滞后过多的副本

由此可见,AR = ISR + OSR

leader副本负责维护和跟踪 ISR 集合中所有follower副本的滞后状态,当follower副本落后太多或失效时,leader副本会把它从 ISR 集合中剔除。如果 OSR 集合中有follower副本追上了leader副本,那么leader副本会把它从 OSR 集合转移至 ISR 集合。默认情况下,当leader副本发生故障时,只有 ISR 集合里的副本才有资格选举为新的leader

HW、LEO是什么?

HW(High Watermark): 高水位,它标识了一个特定的消息偏移量offset,消费者只能拉取到这个offset之前的消息

LEO(Log End Offset): 它标识当前日志文件中下一条待写入消息的offset

/kafka-introduction/partition-hw-leo.jpg
Partition HW LEO

由此可见,Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制。

事实上,同步复制要求所有能工作的follower副本都复制完,这条消息才会被确认为已成功提交,这种复制方式极大的影响了性能。而异步复制的方式下,follower副本异步的从leader副本中复制数据,数据只要被leader副本写入就被认为已经提交成功。在这种情况下,如果follower副本都落后leader副本,突然leader副本宕机,则会造成数据丢失。

Kafka使用的这种ISR的方式则有效的权衡了数据可靠性和性能之间的关系。

Producer

主要工作是向Topic发送消息。但由于副本机制,生产者发送消息怎么样算发送成功呢?在一般的分布式系统中一般采用过半提交的方式确保,既一半以上的副本确认成功才算消息提交成功,但Kafka中并未提供这种机制。Kafka可以通过以下参数配置保障:

  1. Producer提供配置acks参数

    • acks=0 生产者只要将消息发送出去,无需等待任何副本的确认,即算发送成功。此方式吞吐量最大、性能最好,但kafka服务抖动时容易丢消息
    • acks=1 默认。生产者将消息发送出去,只需副本中的leader确认,即算发送成功。此方式吞吐量和性能优秀,但当leader挂时会造成小部分消息丢失
    • acks=all 生产者将消息发送出去,需要ISR中的副本全部确认,即算发送成功。此方式吞吐量和性能差,但稳定性最高,消息不容易丢失
  2. Broker 提供配置replica.lag.time.max.ms参数

    如果一个follower没有发送任何fetch请求,或者至少在这段时间内没有消耗到leaders日志结束偏移量,那么leader将从 ISR 中删除follower

Consumer

Kafka中,1个Partition只能被1个消费线程消费。消费线程主动pull数据,而非Kafka Server主动push数据,这样消费者可以根据自己的消费能力消费数据。如果有消息堆积,也方便开发人员对消费者及时管理。

如果消费线程大于分区数,则多余的消费线程将空闲;如果消费线程小于分区数,则有部分消费线程将消费多个分区的数据;如果消费线程等于分区数,则刚刚好一个消费线程对应一个分区,这也是最理想的情况。

这样做的好处是offset偏移量方便管理且简单,消费数据的分配及提交offset无需事务保障,也提高了效率。

/kafka-introduction/partion-consumer.jpg
Partition Consumer

提交offset

由于消费者是主动pull数据,因此每个分区的offset由对应的消费者线程维护,每个消费线程需要记录消费到当前分区的偏移量offset

在早起Kafka的版本中,每个分区的offset由对应的消费线程维护在zk上。但由于zookeeper的单节点写的特性(只有leader才能处理写请求),所以zookeeper不适合大量数据的频繁写。

之后在Kafka的版本中,每个分区的offset由对应的消费线程维护在__consumer_offset主题中。

结语

以上就是Kafka在整个架构之美的其中一部分,设计还是相当巧妙的。当然涉及到Kafka的东西还有很多。

比如,Kafka在之后版本中逐步在降低对zookeeper的依赖。例如分区副本的选举,在之前的版本中,都是依赖zookeeper操作的。但如果Broker上的Topic过多,一旦Broker宕机将会触发大量Watcher事件,从而引起惊群效应,会导致巨大的服务器性能消耗和网络冲击。因为zookeeper的特性,Kafka选择将各种选举机制都依靠自己解决。

Kafka值得考究、学习和思考的地方还有很多,之后会一一整理分享出来。