2012NETDB: Kafka: a Distributed

2021-04-16  本文已影响0人  Caucher

标题:Kafka:一个用于日志处理的分布式消息队列
久负盛名的kafka,在工业生产中广泛使用的中间件,今天来学习一下原论文,理解下最初的设计思路。本文与2011年产出,发布式LinkedIn公司已经工业化部署Kafka6个月。

ABSTRACT

Kafka设计的初衷是能够低延迟的收集和分发大体量的日志数据,而且同时应用于在线和离线消费,而且要具备可扩展性。

1. Introduction

日志在本文中有更广泛的概念:用户行为日志+系统监控日志。
日志数据一般用于离线分析,但是目前也有部分系统需要实时的消费日志:搜索引擎、推荐系统、推送等。
实时使用这些日志数据并不容易,和事务型数据相比,它们的体量太大了。
用于离线分析时,已经诞生了一些数据聚合运输工具,比如Flume,可以离线的把数据运送到Hadoop或者数仓里去。
为了能够低延迟的消费这些日志,提出了kafka,一个将日志聚合器和消息系统结合起来的中间件。这使得kafka同时有分布式日志聚合器的高吞吐量,又有消息系统的低延迟。

2. Related Work

现有的消息队列的不合适之处:

  1. 现有的这些消息队列和日志收集处理的关注点不一致:消息队列主要关注于事务性、持久性等等,使得系统和API都非常复杂,然而事实上丢失部分日志数据不会有多大影响。
  2. 现有的消息队列不关注吞吐量。
  3. 现有的消息队列对于分布式支持太弱。
  4. 现有的消息队列缓冲区太小了,不适合大量囤积。

现有的日志聚合器主要用于离线传输数据至数仓,不合适之处在于:

  1. 不合适的实现细节暴露;
  2. push模型而非拉模型,容易导致消费端流量过高。

3. Kafka Architecture and Design Principles

某种特定类型的消息流被称为topic,topic存储于多个broker中。消费者可以通过订阅topic,进而从brokers中拉取消息。
从概念上来说消息是简单的,kafka的API尽量维护了这种简单性。producer可以单点或批量生产消息:


image.png

消费者为了消费一个主题的消息,要创建一个或多个消息流。消息将均匀的被拉取到这些消息流中。每个消息流都实现了一个迭代接口,这个迭代器永远不会终止,如果消费完了,就会阻塞线程。
作为分布式的特性:topic会被分布式的存储成多个partition位于多个broker。
作为消息队列的特性:多个producer和多个consumer可以同时生产和消费。


image.png

3.1 Efficiency on a Single Partition

3.2 Distributed Coordination

producer对broker的发送,要不然是轮询,要不然是按照partition key和partition function进行分区。
接下来主要研究consumer和broker的一致性。
首先介绍一个概念:消费组。
消费组(consumer group):一个consumer的集合,可能分布于不同机器,他们共同订阅了同一组topic。在消费组中,每一个topic的partition总是被消费组中的一个consumer消费,然后通过一致性协议,传递给组内其他消费者。不同的消费组之间,不要任何的同步考量。

第一个设计原则:partition是最细粒度的并行。也就是说,在任何时刻,对于某个partition,只能由一个consumer group中的消费者来消费。为了保证负载均衡,可以将一个topic过度分区,超过consumer group中的consumer个数。这样每个consumer都会分到几个分区。

第二个设计原则:同一个消费组的消费者之间用P2P的架构方式。也就是说没有master掌管元信息,而是通过一致性协议来完成的。这里kafka直接用的是zookeeper来接管。
zookeeper的API像是文件系统一样,有路径,子路径,每个路径可以对应值。每个路径有两种模式,临时的/永久的,区别就在于创建(注册)它们的client挂掉的时候,这些路径是否被删除。client还可以向某些路径注册watcher以监控变化。

kafka在四种情况下会向zookeeper注册路径:

  1. broker注册(临时的):host, port, topics & partitions on it
  2. consumer注册(临时的):注册的topics, 属于的consumer group

每个消费组包含两种路径注册:

  1. ownership注册(临时的):ownership是指某个consumer负责消费某个partition。ownership注册包括一个partition的路径,值是consumer id。
  2. offset注册(永久的):一个partition的路径,值是最近消费的offset。

每个consumer都向zookeeper中所有的broker和consumer注册一个Watcher,每当发生变化时(增加/减少),则启动rebalance进程以进行负载均衡。

rebalance:首先释放当前的ownership注册。读取zookeeper中的broker和consumer,分别排序,按照顺序重新确定自己own的partition,尝试注册,注册成功则拉取offset(永久保存于zookeeper),然后开始拉取数据。
如果注册时发现新分配的partition仍然被占用,那么退出进程,释放自己的ownership registry,过一会重试。

image.png

3.3 Delivery Guarantees

  1. kafka是至少一次的传输保障。确切一次的语义需要两阶段提交,过于复杂。大多数时间,kafka也是确切一次的语义,只有一个特殊情况会导致重复,一个consumer宕机了,但是clean的不太彻底,消费了消息但是没能提交给zookeeper。下一个Consumer接替他的时候,那些没提交zookeeper的消息会重复消费。所以程序要么设计成幂等的,要么利用offset检测重复,无论如何也会比两阶段提交更为高效;
  2. kafka可以保证comsumer从一个broker中消费信息是顺序的,但是整体的消息顺序是无法保证的;
  3. kafka为每条消息提供CRC进行验错;
  4. 一旦一个broker彻底坏掉,存储也坏掉,那么这些消息就彻底没了。注意这只是初版的kafka设计,后面kafka补充了replica的设计。

4. Kafka Usage at LinkedIn

此时的kafka的负载均衡部分仍然依赖于外部。


image.png

5. Experimental Results

  1. kafka没有对网络传输有ack,全部的异步发送,只要broker带宽和缓存足够。但这也意味着消息丢了也没人知道。
  2. kafka的消息头很小,只有9Byte。ActiveMq有144Bytes,主要都用于构建B树的信息。
  3. kafka的batch均摊了网络传输开销限制。


    image.png
  1. 仍然是受益于高效传输,较小的消息头。
  2. 无状态的broker消息协议。
  3. Sendfile api的使用。


    image.png
上一篇 下一篇

猜你喜欢

热点阅读