Kafka学习

一 应用场景描述

  我们处理日志是通过ELK来处理的,使用Redis来作为Broker.当业务高峰期来临的时候,Redis队列经常有堵塞的情况发生,经过网上查找资料,有公司使用Kafka来处理日志,据说效率还很高,所以决定先学习一下Kafka,然后再对比测试下Kafka和Redis作为Broker的效率。


二 Kafka简介

  按照Kafka官方的介绍Kafka是一个基于发布订阅模式的高吞吐率的分布式消息系统。Kafka有以下几个术语:

  topic: Kafka维护各种消息的分类

  producer: 向Kafka的topic发布消息的进程

  consumer: 订阅并处理topic中发布的消息的进程

  Kafka集群由一个或者多个服务端组成,每个服务端叫做Broker。


Kafka学习_第1张图片



Topics and Logs

一个topic是Kafka发布消息到的一个类别。对于每一个topic,Kafka集群维护一个如下的分区日志:

Kafka学习_第2张图片


每个分区是一个有序的,连续追加的序号不变的提交日志。分区中的消息都会被分配一个唯一的序号ID来标识这条消息,这个ID叫做offset。

Kafka保留所有发布的消息一段可以配置的时间,无论这些消息是否被消费掉。例如,日志保留时间设置成为两天,那么当一条消息发布后的两天内可以被消费,过期后就被丢弃掉以释放掉磁盘空间。Kafka的性能相对于数据大小是一个常数,所以保留很多数据不是问题。

实际上,在一个per-consumer basis上保留的唯一的元数据是消费者在日志中的位置,叫做offset.这个offset由消费者控制,通常一个消费者在读取消息的时候会线性地推进offset。但是实际上这个位置是由消费者控制,并且这个消费者可以消费任何序列的消息。



Distribution

日志的所有分区分布到Kafka集群的各个服务端上,每个服务端以共享分区的方式处理数据和请求。每个分区在配置的用于容错的服务端进行复制。

每个分区有一个服务端扮演leader的角色,0个或多个服务端扮演follower的角色。Leader处理所有的读写请求,而follower只是被动地复制leader。如果leader挂掉,其中一个follower会自动变成新的leader。每个服务端扮演它的一些分区的leader和另一些分区的follower,所以整个Kafka集群内部的负载是相对平衡的。


Producers

生产者发布消息到选择的topic,生产者负责选择哪些消息分配到topic的哪些分区上。


Consumers

传统的消息系统主要有两种模型:消息队列模型和发布-订阅模型。在一个队列中,一个连接池的消费者肯能从一个服务端读取消息,然后每条消息又转到其中一个消费者。在发布-订阅模式中,消息被广播到所有的消费者。Kafka提供一个单个消费者抽象来概括前面两种模型---那就是consumer group.

每个消费者标记他们自己一个消费者组名称。发布到一个topic的每条消息被投递到其中一个具有相同订阅消费者组名称的消费者实例上。消费者实例可以是不同的进程或者分布到不同的机器上。

如果所有的消费者实例的consumer group相同,那么这种工作方式就和消息队列模型相同,消息在不同的消费者上进行负载均衡。

如果所有的消费者实例的consumer group不相同,那么这种方式就和发布-订阅模型相同,所有消息被广播到所有的消费者上。


一个传统队列在服务器上保存消息是按照顺序的,如果有多个消费者从队列中消费,那么消息是按照存储的顺序转出的。然而,虽然服务端是按照顺序转出消息,但是这些消息却是被异步投递到消费者端。所以它们到达消费者端可能是乱序的。这就意味着当并发消费时,消息的序列就就消失了。为了解决这个问题,消息系统有一个“独占消费者”的概念,只允许一个进程从一个队列中消费,但是这样的话就不能并行处理消息了。

Kafka这方面做得比较好,通过一个慨念--topic中的分区--来实现并行消费。Kafka可以在一个连接池中的消费者进程间提供次序保证和负载均衡的功能。这是通过Kafka分配topic中的分区到一个消费者组中的消费者来实现的,这样每个分区准确地被一个消费者组中的一个消费者消费。这样做我们保证这个消费者是这个分区的唯一的读取端进而顺序消费其他中的数据。由于有很多分区,这样还可以在很多消费者实例间均衡负载。需要注意的是,一个消费者组中的消费者数量不能多于分区的数量。

Kafka只提供分区内的消息顺序保证,不提供不能分区间的消息次序保证。



Kafka学习_第3张图片


Guarantees

Kafka提供以下保证:

被发送到特定topic分区的消息会以它们被送出的顺序追加

消息者实例看到的消息在日志中是按照顺序存储的

对于一个topic有N个复制成员,那么最多允许N-1个成员挂掉。



三 Kafka的设计思路

1.Motivation

Kafka被设计成一个处理能够处理大型公司的各种实时数据的通用平台。需要考虑到以下几个用户案例:

具有高吞吐率来处理大容量事件流数据的功能,例如实时的日志数据聚合

可以优雅地处理大量积压的数据来支持周期性地从离线系统加载数据

需要低延迟投递消息来处理很多传统的消息系统案例

可以支持分区,分布式和实时处理

提供容错机制

2.Persistence

Don't fear the filesystem!

Kafka严重依赖文件系统来存储和缓冲消息。有个通用的看法就是“磁盘比较慢”,所以这就会让人怀疑提供一个持久化的架构可以提供有竞争力的性能。实际上磁盘性能比人们所期待的更慢也更快,这取决于如何使用这些磁盘。一个良好设计的磁盘架构可以同网络一样快。


Constant Time Suffices

消息系统用到的持久化数据结构通常是一个关联BTree的per-consumer队列或者其他通用用途的用来维护消息有关的元数据的随机访问的数据结构。BTree是可用的最通用的数据结构,它可以在消息系统中支持多种事务型或者非事务型的语义。


3.Efficency

Kafka的开发人员在Kafka的效率方面投入了很大的努力。首要的用户案例就是处理网页活动数据,这些数据一般数量巨大,每个页面访问都会产生很多写入数据。而且我们假设每条发布的消息被至少一个消费者读取,因而我们力图使消费过程尽可能简单。

有两大原因造成系统磁盘低效率:太多小型I/O操作和过多的字节拷贝。

小型I/O操作问题发生在服务端和客户端以及服务端自身的持久化操作的过程中。为了避免这个问题,Kafka的协议被构建成一个"message get"抽象,自然地将所有的消息分组到一起。这允许网络请求对消息进行分组并且摊销网络往返的压力而不是一次只发送单条消息。服务端一口气依次追加消息块到的日志,消费者一次从服务端获取很大的线性消息块。这个简单的优化会产生数量级别的性能提升。批处理导致更大的网络数据包,更大的顺序磁盘操作,连续的内存数据块等等,所有这些允许Kafka将一大批随机消息写入转化成线性写入然后流向到消费者。

另一个不高效的原因是字节拷贝。在较低的消息流速率情况下,这个不是个问题。但是当速率很大时,字节拷贝对处理效率的影响就是巨大的。为了避免字节拷贝的问题,Kafka采用了一个标准的二进制消息格式,这种格式在broker,生产者和消费者之间共享,所以数据块在它们之间传输时不需要作任何的更改。

Broker维护的消息日志就是一个目录下的一些文件,每个日志文件是由一系列已经被写入到磁盘具有生产者和消费者使用到的相同格式的消息集合。维护这个通用格式需要对最重要的操作进行优化:那就是网络传输持久化的日志数据块。现代UNIX操作系统提供一些高度优化过的代码来从页面缓存传输数据到socket,Linux系统是通过senfile系统调用实现的,比起read和write,sendfile传输数据的效率更高,因为它是直接在内核空间上处理的,不需要经过用户空间。

为了理解sendfile的影响,理解数据从文件传输到socket的过程是很重要的:

  a.操作系统从磁盘读入数据到内核空间的页面缓存

  b.应用程序从内核空间读入数据到指定的用户空间缓冲区

  c.应用程序写回数据到socket缓冲区的内核空间中

  d.操作系统从socket缓冲区复制数据到NIC缓冲区


以上几个步骤显然很低效,有四次拷贝和两次系统调用。使用sendfile,再拷贝的操作就可以省掉,直接从操作系统的页面缓存复制数据到NIC缓冲区。

结合页面缓存和sendfile,在一个Kafka集群上看不到什么磁盘读入。


End-to-end Batch Compression

在一些案例中瓶颈并不是CPU和磁盘而是网络带宽。Kafka支持对消息进行压缩处理。



4.The Producer

Load Blancing

生产者直接发送数据到分区的Leader角色的broker不需要经过任何中间路由层,为了帮助生产者这样发送数据,所有的Kafka节点可以应答有哪些服务端是存活的,topic的分区的leader在哪里以使生产者可以直接地发送请求到对应的服务端。

客户端控制发布消息到哪个分区上。这种可以通过实现一个随机的负载均衡算法或者一个特定的分区方法来处理。


Asynchonous Send

批处理是提升效率的一个巨大驱动力,为了启动批处理,Kafka的生产者将尝试在内存中计算数据并且在一次请求中发送大批量数据。


5.The Consumer

Kafka的消费者从想要消费的分区的leader上获取请求。消费者在每次请求中指定消息日志的offset然后接收回从那个offset开始的一段日志块。


Push vs Pull

Kafka在最初设计时考虑的一个问题就是是否应该让消费者从broker拉取消息还是从broker推送消息到消费者。在这方面,Kafka设计的是从生产者推送数据到broker然后由消费者从broker拉取数据。



Consumer Position

跟踪消费了什么是消息系统的一个关键性能指标。大多数消息系统在broker上保存消费了哪些消息的元数据。也就是说,一个消息被移交到消费者后,broker要么立即在本地记录这个事件或者它可能等待消费者确认。

或许是生产者和消费者对消费了哪些消息达成一致不是一个小问题。如果broker记录一条通过网络移交出去立即被消费掉的消息时,那么如果消费者处理这条消息失败,这条消息将会丢失。为了解决这个问题,很多消息系统会增加了一个确认特征,发送出去没有被消费的消息被标记sent。Broker等待一个从消费者返回的确认信息后标记这条消息为consumed.这个策略解决了消息丢失的问题,但是又会产生新的问题。首先,如果消费者在发送确信信息之前处理一条消息失败,那么这条消息会被消费两次。第二个问题是关于性能,broker必须为每条消息保留多个状态,一些诡异的问题必须要处理,比如那些发送出去但是没有得到确认的消息该如何处理。

Kafka以不同的方式处理这些问题。Kafka把topic分割成一系列完全有序的分区,每个分区在任何给定的时间都被一个消费者消费。意味着每个分区的消费者的postion是一个单个整数。这使得每个分区有哪些消息被消费掉的状态很简单,就是一个整数来表示。
















参考文档:

http://kafka.apache.org/

http://kafka.apache.org/documentation.html#introduction


你可能感兴趣的:(kafka)