背景

学习事件总线(EventBus)涉及到MQ的实现,于是想做个基于Kafka的实现,顺便整理基础概念。

EventBus

EventBus 是一种事件发布订阅模式,通过 EventBus 我们可以很方便的实现解耦,将事件的发起和事件的处理的很好的分隔开来,很好的实现解耦。

Kafka

Kafka是一个分布式的、可分区的、可复制的消息系统。它提供了普通消息系统的功能,但具有自己独特的设计。它拥有以下三大核心功能:

  • 发布和订阅数据流,类似于传统消息队列(RabbitMQ,RocketMQ)的功能

  • 以容错的方式存储数据流的功能

  • 实时处理数据流的功能

    为了支持以上的三大核心功能,Kafka有四个核心的API:

  • The Producer API 允许一个应用程序发布一串流式的数据到一个或者多个Kafka topic。

  • The Consumer API 允许一个应用程序订阅一个或多个 topic ,并且对发布给他们的流式数据进行处理。

  • The Streams API 允许一个应用程序作为一个流处理器,消费一个或者多个topic产生的输入流,然后生产一个输出流到一个或多个topic中去,在输入输出流中进行有效的转换。

  • The Connector API 允许构建并运行可重用的生产者或者消费者,将Kafka topics连接到已存在的应用程序或者数据系统。比如,连接到一个关系型数据库,捕捉表(table)的所有变更内容。

Kafka数据流

Kafka

基础概念

  • Broker
    一个Kafka节点就是一个Broker,多个broker可以组成一个Kafka集群,一个Broker可以容纳多个Topic
  • Topic
    属于特定类别的消息流称为Topic, 数据存储在Topic中,可以理解为一个队列,但是只能是一类消息
  • Partition
    Topic物理上的分区,一个Topic可以分为多个Partition,每个Partition是一个有序的队列。每个Partition都对应唯一的消费者!!!
  • Offset
    每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到Partition中。Partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息.同一个Partition的Offset是被顺序消费的
  • Producer
    消息生产者,向Kafka中发消息的客户端。Producer将消息发布到指定的Topic,也可以指定Partition。
  • Consumer
    消息消费者,从Kafka中取消息的客户端。Kafka中的Consumer采用poll模型

Topic

Topic 就是数据主题,是数据记录发布的地方,可以用来区分业务系统。Kafka中的Topics总是多订阅者模式,一个Topic可以拥有一个或者多个消费者来订阅它的数据。

消息在物理上是以文件的方式存储的,它们按照不同的Topic进行分文件存储。每一个Topic同时又被划分为多个Partition,每个Partition对应着一个文件(逻辑上的说法,物理上由多个segment file组成),它存储着所有发往这个Partition的消息。
对于每一个Topic, Kafka集群都会维持一个分区日志,如下所示:

log_anatomy

每个分区都是有序且顺序不可变的记录集,并且不断地追加到结构化的commit log文件。分区中的每一个记录都会分配一个id号来表示顺序,我们称之为offset,offset用来唯一的标识分区中每一条记录。Kafka并没有额外的索引机制来存储offset,因此这意味着Kafka几乎不允许对数据进行随机读写。

Kafka将topic划分为多个partition进行存储拥有两个好处:

  • 消息存储扩容。 一个文件的存储大小是有限的,但在集群中的多个文件的存储就可以大大增加一个topic能够保存的消息数量。
  • 并行读写。 通过多个partition文件存储消息,意味着producer和consumer可以并行的读写一个topic。

Consumer消费消息时,通过指定的offset来定位下一条要读取的消息。值得注意的是,offset的维护是由Consumer全权控制的。Kafka集群只负责根据Consumer传入的offset来返回对应的消息。如下图所示:

log_consumer

Kafka不会立刻删除已经被消费的消息,它会根据broker中的配置来决定多久清理一次。当broker中配置的时间到达时,不论消息是否被消费,Kafka都会清理磁盘空间。

Producer

生产者可以将数据发布到所选择的topic(主题)中。生产者负责将记录分配到topic的哪一个 partition(分区)中。可以使用循环的方式来简单地实现负载均衡,也可以根据某些语义分区函数(例如:记录中的key)来完成。

Consumer

消费者使用一个消费组名称来进行标识,发布到topic中的每条记录被分配给订阅消费组中的一个消费者实例.消费者实例可以分布在多个进程中或者多个机器上。

如果所有的消费者实例在同一消费组中,消息记录会负载平衡到每一个消费者实例.

如果所有的消费者实例在不同的消费组中,每条消息记录会广播到所有的消费者进程.

传统的消息队列提供两种消息消费模式:

  • 队列模式:一条消息只能被多个消费者中的一个消费。
  • 发布订阅模式:一条消息能够被多个消费者同时消费。

Kafka为了支持这两种消费模型,提出了消费者组(consumer group)的概念。如下图所示:

consumer-groups

如图,这个 Kafka 集群有两台 server 的,四个分区(p0-p3)和两个消费者组。消费组A有两个消费者,消费组B有四个消费者。

通常情况下,每个 topic 都会有一些消费组,一个消费组对应一个”逻辑订阅者”。一个消费组由许多消费者实例组成,便于扩展和容错。这就是发布和订阅的概念,只不过订阅者是一组消费者而不是单个的进程。

在Kafka中实现消费的方式是将日志中的分区划分到每一个消费者实例上,以便在任何时间,每个实例都是分区唯一的消费者。维护消费组中的消费关系由Kafka协议动态处理。如果新的实例加入组,他们将从组中其他成员处接管一些 partition 分区;如果一个实例消失,拥有的分区将被分发到剩余的实例。

Kafka 只保证分区内的记录是有序的,而不保证主题中不同分区的顺序。每个 partition 分区按照key值排序足以满足大多数应用程序的需求。但如果你需要总记录在所有记录的上面,可使用仅有一个分区的主题来实现,这意味着每个消费者组只有一个消费者进程。

Docker部署

  • zookeeper启动
1
docker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper
  • kafka启动
1
docker run -d -v /e/docker/kafka:/kafka --name kafka -p 9092:9092 --link zookeeper -e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -e KAFKA_ADVERTISED_HOST_NAME=192.168.66.23 -e KAFKA_ADVERTISED_PORT=9092 wurstmeister/kafka:latest
  • kafka-manager启动
1
docker run -d -p 9000:9000 --link zookeeper -e ZK_HOSTS="zookeeper:2181" -e APPLICATION_SECRET=letmein sheepkiller/kafka-manager