一 应用场景描述

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


二 Kafka简介

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

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

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

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

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


producer_consumer.png



Topics and Logs

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

log_anatomy.png


每个分区是一个有序的,连续追加的序号不变的提交日志。分区中的消息都会被分配一个唯一的序号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只提供分区内的消息顺序保证,不提供不能分区间的消息次序保证。



consumer-groups.png


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是一个单个整数。这使得每个分区有哪些消息被消费掉的状态很简单,就是一个整数来表示。



四 Kafka的基本操作

下载安装Kafka

wget http://www-us.apache.org/dist/kafka/0.10.0.0/kafka_2.11-0.10.0.0.tgz
tar zxvf kafka_2.11-0.10.0.0.tgz 
cd kafka_2.11-0.10.0.0

Kafka使用很简单,解压后执行文件都在bin目录下,配置文件都在conf目录下,libs目录是各种jar文件

1.Basic Kafka Operations


Adding and removing topics

# bin/kafka-topics.sh --zookeeper localhost:2181  --create --topic testtopic --partitions 20 --replication-factor 3

replication-factor 决定有多少个服务器可以复制写入的消息,如果设置为3,那么最多可以有2台服务器挂掉而不丢失数据,所以建议设置这个参数为2或者3,因为可以在不中断消息消费的情况下重启服务


patitions  这个参数控制每个topic可以被分隔成多少个logs,patitions的数量有以下几个影响:

        首先,必须要保证一台服务器可以分配一个完整的partition,也就是说如果有20个分区,,那么完整的数据集不能在20台以上的服务器上处理。

        最后,partition的数量会影响最大并发消费者数量。


每个shareded partition存放在config/server.properties配置文件中的logs.dir目录下。例如

/tmp/kafka-logs/testtopic-18 这种形式,testtopic是topic的名称,18是partition id。

因为一个普通的目录文件的名字长度不能超过255个字符,所以topics的名字长度也有限制。假定partitions的最大数量不能超过100000,因此,topics名字长度不能超过249个字符,一个字符留给topics分片目录的 - 符合,另外5个数字字符留给分片目录的partition id



Modifying topics


增加partitions

# bin/kafka-topics.sh --zookeeper localhost:2181 --alter  --topic testtopic --partitions 50 
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!


Be aware that one use case for partitions is to semantically partition data, and adding partitions doesn't change the partitioning of existing data so this may disturb consumers if they rely on that partition. That is if data is partitioned by hash(key) % number_of_partitions then this partitioning will potentially be shuffled by adding partitions but Kafka will not attempt to automatically redistribute data in any way.



添加configs

# bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic testtopic --config "segment.bytes=1073741824"
WARNING: Altering topic configuration from this script has been deprecated and may be removed in future releases.
         Going forward, please use kafka-configs.sh for this functionality
Updated config for topic "testtopic".



提示说以后应当用kafka-config.sh来操作


删除配置

# bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic testtopic --delete-config segment.bytes
WARNING: Altering topic configuration from this script has been deprecated and may be removed in future releases.
         Going forward, please use kafka-configs.sh for this functionality
Updated config for topic "testtopic".



默认删除topics没有开启,需要在配置文件中指定


delete.topic.enable=true


Kafka目前不支持减少分区数量



Graceful shutdown

Kafka集群能够自动地检测到任何broker关机或者失效,然后为失效的这台broker上的partitions选择出新的leaders。重新选举leaders会在一台broker失效或者间断维护或者配置更改需要重启的时候发生。

Kafka使用更优雅地方式关掉服务而不是直接杀死进程,当Kafka服务优雅地关闭有以下两点好处:


  它将同步所有的日志到磁盘,避免重启时任何日志恢复操作。日志恢复操作会花费时间,所以优雅地关闭服务加速了重启速度

  在关闭服务之前,Kafka会迁移这台服务器上的任何Leader角色的分区到其他副本上。这样可以使leadership转移更快并且减小每个partition的不可达时间到几毫秒。


无论何时Kafka服务停掉或者被强制杀掉,同步日志操作都会自动地发生。但是迁移leadership需要一个特别的设置:


controlled.shutdown.enable=true


注意controlled shutdown 只有当这台broker上的所有partitions有replicas才会成功。也就是说--replication-factor参数的值要大于1



Balancing leadership


任何时候当一个broker停掉或者崩溃,这个broker上的partitions的leadership会立即转移到其他replicas上。这就意味着默认情况下,当一个broker重启后它只是一个follower的角色,不能够向客户端提供读写操作。


为了避免这种不均衡的状态,Kafka可以按照优先级顺序设置一组replicas。例如,列出的replicas的顺序是1,5,9  那么结点1成为leader的优先级就会高于结点5和结点9。因为结点1先出现在replica list中。

You can have the Kafka cluster try to restore leadership to the restored repicas by running:

> bin/kafka-preferred-replica-election.sh --zookeeper zk_host:port/chroot



可以在配置文件中设置

auto.leader.rebalance.enable=true


Balacing Replicas Across Racks


rack awareness特性将同一个partition的replicas分布不同的racks。这样就可以确保当一个机柜出现故障时,Kafka集群不会丢失数据。同样,对于EC2,可以设置跨不同的区域。


可以在配置文件中指定broker属于哪个broker

broker.rack=my-rack-id

创建,修改或者复制topic的操作都是分布式的,rack constraint会尽量确保partitions会复制到能够复制的racks上,一个partition会分布在 min(racks,replication-factor)个racks上。


分配不同分区的副本的算法会确保每个broker的leaders的数量是固定的,不管brokers是如何分布在不同的机柜上的。

但是如果不同的机柜分配有不同数量的brokers,那么replicas的分配就会不均衡。brokers数量较少的racks会分配到更多的replicas。就是说它们将使用更多的存储空间,复制更多的资源。所以最好设置每个rack相同数量的brokers。



Mirroring data between clusters

Kafka把Kafka集群之间的复制数据操作叫做mirroring,为了避免混淆单个集群不同结点之间的复制操作。Kafka自带一个工具用于镜像复制不同Kafka集群间的数据。这个工具从一个源集群读入数据,然后写入数据到目的集群。



mirror-maker.png


这种镜像复制的常用案例就是在另一个数据中心提供一个当前数据中心Kafka集群的副本。


镜像复制工具从源集群的topic读入数据,然后写入数据到目的集群中同名的topic中。事实上,mirror maker更像是一个Kafka消费者和生产者绑定在一起。


源集群和目的集群是完全独立的整体。它们可以有不同数目的分区和偏移量。基于这个原因,镜像集群不是真的为了提供容错机制,因为消费者位置可能不同。


 > bin/kafka-mirror-maker.sh
       --consumer.config consumer-1.properties --consumer.config consumer-2.properties
       --producer.config producer.properties --whitelist my-topic


--whitelist 指定需要镜像复制的topics ,topics的名称支持使用Java正则表达式

例如  --whitelist 'A|B' 表示镜像复制topic A和topic B

  --whitelist '*'   表示复制所有topics

使用 --blacklist 屏蔽不需要镜像复制的topics


设置 auto.create.topics.enable=true  可以自动镜像复制所有topics的数据



Checking consumer position

有时查看消费的postition很有用。Kafka提供一个工具可以显示所有的消费者在一个消费者群组中的position。


> bin/kafka-run-class.sh kafka.tools.ConsumerOffsetChecker --zookeeper localhost:2181 --group test
Group           Topic                          Pid Offset          logSize         Lag             Owner
my-group        my-topic                       0   0               0               0               test_jkreps-mn-1394154511599-60744496-0
my-group        my-topic                       1   0               0               0               test_jkreps-mn-139415


注意,0.9.0以后kafka.tools.ConsumerOffsetChecker已经废弃了,应该使用kafka.admin.ConsumerGroupCommand或者bin/kafka-consumer-groups.sh


Managing Consumer Groups

使用ConsumerGroupCommand,可以列出,删除或者显示消费者群组。

 > bin/kafka-consumer-groups.sh --zookeeper localhost:2181 --list

test-consumer-group


> bin/kafka-consumer-groups.sh --zookeeper localhost:2181 --describe --group test-consumer-group

GROUP                          TOPIC                          PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             OWNER
test-consumer-group            test-foo                       0          1               3               2               test-consumer-group_postamac.


如果使用新的消费者API,需要加一个参数 --new-consumer


> bin/kafka-consumer-groups.sh --new-consumer --bootstrap-server broker1:9092 --list



Expanding your cluster


向一个Kafka集群中添加服务端很简单,只需要分配一个唯一的broker id并且启动。但是这些这些服务端不会自动地被分配任何分区数据,所以除非分区被移动到这些服务端上,要不然它们直到新的topics被创建时不会有任何事做。所以,通常当添加一些新服务端到集群后,想要迁移一些现有数据到这些服务端上。


迁移数据的过程最初是手动执行,但是会自动地完成。Kafa会添加这台新的broker服务端作为一个需要迁移的partition的follower,并且允许它完全复制这个partition的已有数据。


分区重新分配工具可以用来在不同brokers之间移动分区。理想情况下,所有brokers的数据负载和分区大小应该是均衡的。分区重新分配工具不能够自动地学习在一个Kafka集群中的数据分布并且来回移动分区使数据分区处于均衡状态。所以,管理员不得不确认哪些topics或者分区应当被移动。


分区重新分配工具可以以3种相互排斥的模式运行:

 --generate    

       这种模式下,给定一系列topics和一系列brokers,the tool generate a candidate reassignment to move all partitions of the sepecified topics to the new brokers。这种模式只是为指定的一系列topics和目标brokers提供一个简便方法来生成一个分区重新分配的计划


 --execute     

       In this mode,the tool kick off the reassignment of partitions based on the user provided reassignment plan.(using the --reassignment-json-file option).This can either be a custom reassignment plan hand crafted by the admin or provided by using the --generate option


 --verify

       In this mode,the tool verifies the status of the reassignment for all partitions listed during the last --execute.The status can be either of successfully completed,failed or in progress.


Automatically migrating data to new machines

分区再分配工具可以用来移动一些当前brokers上的topics到新加入到集群的brokers。这对于扩展当前集群很有用,因为比起一次移动一个分区,移动整个topics到新的brokers更容易些。做这种操作时,需要指定需要移动的topics和一组新brokers。然后这个工具会均衡地将所有的分区分布到新的brokers上。在数据移动的工程中,每个topic的复制成员数量保持不变。


下面的案例中,使用 bin/kafka-reassign-partitions.sh 将foo1和foo2两个topics的所有分区移动到新的brokers 5,6. 到最后,foo1和foo2两个topics的所有分区只会存在于brokers5,6上


> cat topics-to-move.json
{"topics": [{"topic": "foo1"},
            {"topic": "foo2"}],
 "version":1
}



> bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --topics-to-move-json-file topics-to-move.json --broker-list "5,6" --generate
Current partition replica assignment

{"version":1,
 "partitions":[{"topic":"foo1","partition":2,"replicas":[1,2]},
               {"topic":"foo1","partition":0,"replicas":[3,4]},
               {"topic":"foo2","partition":2,"replicas":[1,2]},
               {"topic":"foo2","partition":0,"replicas":[3,4]},
               {"topic":"foo1","partition":1,"replicas":[2,3]},
               {"topic":"foo2","partition":1,"replicas":[2,3]}]
}

Proposed partition reassignment configuration

{"version":1,
 "partitions":[{"topic":"foo1","partition":2,"replicas":[5,6]},
               {"topic":"foo1","partition":0,"replicas":[5,6]},
               {"topic":"foo2","partition":2,"replicas":[5,6]},
               {"topic":"foo2","partition":0,"replicas":[5,6]},
               {"topic":"foo1","partition":1,"replicas":[5,6]},
               {"topic":"foo2","partition":1,"replicas":[5,6]}]
}



这个工具生成一个candidate assignment,将foo1,foo2的所有分区移动到brokers 5,6上

需要注意,现在分区移动操作还没有开始,它只是告诉你目前的分区分配和将要执行的分区分配。

当前的分区分配应该被保存以免想要还原回去,新的分区分配应该被保存到json文件作为 --excute 选项的输入文件


> bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file expand-cluster-reassignment.json --execute
Current partition replica assignment

{"version":1,
 "partitions":[{"topic":"foo1","partition":2,"replicas":[1,2]},
               {"topic":"foo1","partition":0,"replicas":[3,4]},
               {"topic":"foo2","partition":2,"replicas":[1,2]},
               {"topic":"foo2","partition":0,"replicas":[3,4]},
               {"topic":"foo1","partition":1,"replicas":[2,3]},
               {"topic":"foo2","partition":1,"replicas":[2,3]}]
}

Save this to use as the --reassignment-json-file option during rollback
Successfully started reassignment of partitions
{"version":1,
 "partitions":[{"topic":"foo1","partition":2,"replicas":[5,6]},
               {"topic":"foo1","partition":0,"replicas":[5,6]},
               {"topic":"foo2","partition":2,"replicas":[5,6]},
               {"topic":"foo2","partition":0,"replicas":[5,6]},
               {"topic":"foo1","partition":1,"replicas":[5,6]},
               {"topic":"foo2","partition":1,"replicas":[5,6]}]
}



最后,使用--verify 选项检查分区分配情况。

> bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file expand-cluster-reassignment.json --verify
Status of partition reassignment:
Reassignment of partition [foo1,0] completed successfully
Reassignment of partition [foo1,1] is in progress
Reassignment of partition [foo1,2] is in progress
Reassignment of partition [foo2,0] completed successfully
Reassignment of partition [foo2,1] completed successfully
Reassignment of partition [foo2,2] completed successfully



Custom partition assignment and migration


分区重新分区工具也可以用来选择性地移动一个分区的副本到一组brokers上。以这种方式使用这个工具,假设用户已经知道reassignment plan并且不需要再使用工具生成一个分区重新分配计划了。


以下例子中,移动topic foo1的partition 0到brokers 5,6,topic foo2的partition 1到brokers 2,3



> cat custom-reassignment.json
{"version":1,"partitions":[{"topic":"foo1","partition":0,"replicas":[5,6]},{"topic":"foo2","partition":1,"replicas":[2,3]}]}



> bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file custom-reassignment.json --execute
Current partition replica assignment

{"version":1,
 "partitions":[{"topic":"foo1","partition":0,"replicas":[1,2]},
               {"topic":"foo2","partition":1,"replicas":[3,4]}]
}

Save this to use as the --reassignment-json-file option during rollback
Successfully started reassignment of partitions
{"version":1,
 "partitions":[{"topic":"foo1","partition":0,"replicas":[5,6]},
               {"topic":"foo2","partition":1,"replicas":[2,3]}]
}



bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file custom-reassignment.json --verify
Status of partition reassignment:
Reassignment of partition [foo1,0] completed successfully
Reassignment of partition [foo2,1] completed successfully



Decommissioning brokers


kafka-reassign-partitions.sh 这个工具不能自动为剔除brokers生成一个reassignment plan。因此,管理员不得不手动将需要剔除的brokers上的所有分区的副本移动到其他brokers上。这样很烦人,因为必须确保所有的副本没有全部只是移动到其他brokers中的一台。这个问题在Kafka的将来版本中会有所改进。


Increasing replication factor

为一个已有的分区增加复制成员很简单。仅需要在自定义分区重新分配的json文件中指定然后使用kafka-reassign-partitions.sh 结合 --execute参数来为指定的分区增加复制成员.


以下的案例中将会增加topic foo的partition 0的复制成员从1个增加到3个.增加复制成员之前,这个分区的唯一一个副本存在在broker 5上,我们将要在brokers 6和7上为它添加副本.


> cat increase-replication-factor.json
{"version":1,
 "partitions":[{"topic":"foo","partition":0,"replicas":[5,6,7]}]}



> bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file increase-replication-factor.json --execute
Current partition replica assignment

{"version":1,
 "partitions":[{"topic":"foo","partition":0,"replicas":[5]}]}

Save this to use as the --reassignment-json-file option during rollback
Successfully started reassignment of partitions
{"version":1,
 "partitions":[{"topic":"foo","partition":0,"replicas":[5,6,7]}]}



bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file increase-replication-factor.json --verify
Status of partition reassignment:
Reassignment of partition [foo,0] completed successfully



> bin/kafka-topics.sh --zookeeper localhost:2181 --topic foo --describe
Topic:foo	PartitionCount:1	ReplicationFactor:3	Configs:
	Topic: foo	Partition: 0	Leader: 5	Replicas: 5,6,7	Isr: 5,6,7




Setting quotas


quota.producer.default=10485760
quota.consumer.default=10485760


设置每个生产者和消费者的配额为10MB/sec


Kafka也允许为单个客户端设置配额

> bin/kafka-configs.sh  --zookeeper localhost:2181 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048' --entity-name clientA --entity-type clients
Updated config for clientId: "clientA".



查看配额

> ./kafka-configs.sh  --zookeeper localhost:2181 --describe --entity-name clientA --entity-type clients
Configs for clients:clientA are producer_byte_rate=1024,consumer_byte_rate=2048



2.Datacenters

建议每个数据中心部署一个Kafka集群,单个数据中心中的应用连接本地数据中心的Kafka集群,不同的数据中心之间使用Kafka提供的集群镜像复制工具复制数据。


Kafka在生产者和消费者中都是批处理数据的。所以即使在一个高延迟的网络连接中也可以达到高吞吐量。为了达到高吞吐量,需要在生产者,消费者和broker上增大TCP的socket缓冲区大小,使用socket.send.buffer.bytes 和socket.receive.buffer.bytes 参数





通常不建议单个Kafka集群的结点分布在多个高网络延迟的数据中心。




3.Kafka配置


Important Client Configrations


最重要的生产者配置参数

 compression.type     生产者端生产的数据的压缩类型。默认值是none,即不压缩。有效的值为none,gzip,snappy,lz4  压缩是对批处理数据操作的,所以批处理的效率也会影响到压缩的效率,批处理越多压缩压过越好

  


 batch.size

             生产者会试图将发送给同一个patition的多个记录一起批量处理成更小的请求。这对客户端和服务端的性能都有所帮助。这个参数控制一次批处理的大小,以字节表示。如果批量的记录大小大于这个值则不会被试图一起处理。比较小的batch.size 会减小吞吐量。大点的batch.size可能会使用内存有点浪费,因为Kafka会总是分配batch size大小的缓冲区用来预先存放额外的消息记录


 sync  vs async production



最重要的消费者配置


fetch.min.bytes

     设置一次获取消息服务端应该返回的最小数据量。如果服务端积累的数据量不够,那么会等到数据量积累够了再回复这个请求。默认的值是1个字节,意味着一旦有一个字节的数据可获取时,服务端立马回应请求。设置这个值大于1个字节会引致服务端等待积累更大的数据量,这样可以改善服务端的吞吐量但是也会增加延迟



max.partition.fetch.bytes

     设置服务端返回每个partition的最大数据量。一个请求使用的最大内存大小为 #partitions * max.partition.fetch.bytes  这个值至少应该与服务端允许的最大消息大小等同或者更大。生产者可以发送比消费者能够获取更大的消息数据量,如果这种情况发生,消费者可能卡住尝试获取更大的消息量。




A Production Server Config


 

# Replication configurations
num.replica.fetchers=4
replica.fetch.max.bytes=1048576
replica.fetch.wait.max.ms=500
replica.high.watermark.checkpoint.interval.ms=5000
replica.socket.timeout.ms=30000
replica.socket.receive.buffer.bytes=65536
replica.lag.time.max.ms=10000

controller.socket.timeout.ms=30000
controller.message.queue.size=10

# Log configuration
num.partitions=8
message.max.bytes=1000000
auto.create.topics.enable=true
log.index.interval.bytes=4096
log.index.size.max.bytes=10485760
log.retention.hours=168
log.flush.interval.ms=10000
log.flush.interval.messages=20000
log.flush.scheduler.interval.ms=2000
log.roll.hours=168
log.retention.check.interval.ms=300000
log.segment.bytes=1073741824

# ZK configuration
zookeeper.connection.timeout.ms=6000
zookeeper.sync.time.ms=2000

# Socket server configuration
num.io.threads=8
num.network.threads=8
socket.request.max.bytes=104857600
socket.receive.buffer.bytes=1048576
socket.send.buffer.bytes=1048576
queued.max.requests=16
fetch.purgatory.purge.interval.requests=100
producer.purgatory.purge.interval.requests=100



Java Version

从安全角度考虑,建议使用最新版本JDK1.8。Linkedin目前使用JDK1.8u5,

-Xmx6g -Xms6g -XX:MetaspaceSize=96m -XX:+UseG1GC
-XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:G1HeapRegionSize=16M
-XX:MinMetaspaceFreeRatio=50 -XX:MaxMetaspaceFreeRatio=80



LinkedIn 最繁忙的集群状态如下:

   60 brokers

   50K partitions

   800K messages/sec in

   300MB/sec inbound,1GB/sec+ outbound


集群中的所有brokers %90的GC 时间大约为21ms,每秒少于1个YGC



4.Hardware and OS

LinkedIn 使用Intel Xeon,24G内存

需要配置足够的内存用户缓冲读写操作。简单预估需要内存为 write_throughput*30,即需要的的内存最少应该是缓冲30每秒的消息数据量


磁盘吞吐量很重要,LinkedIn使用8x7200 rpm SATA磁盘。



OS


运行Kafka集群不需要过多的OS层面的调优,但是有两个重要的配置:


 文件描述符限制    Kafka为日志片段和打开的连接使用文件文件描述符。如果一个broker拥有很多分区,考虑这个broker用来跟踪所有的日志片段需要的文件描述符加上broker的连接数需要的文件描述符至少为 (number_of_patitions) * (partition_size/segment_size) 建议至少允许的文件描述符在100000以上


 最大socket缓冲区大小



Disks and Filesytem

建议使用多块磁盘增加磁盘吞吐量,并且将Kafka的数据和应用日志以及OS其他文件系统活动存放在不同的磁盘上以减少延迟



Application vs OS Flush Management

Kafka总是立即写入数据到文件系统并且可以配置刷新策略何时强制将操作系统缓存中的数据刷新到磁盘。刷新策略可以是隔一段时间或者写入了一定数量的消息。


Kafka必须最后调用fsync来知道数据是否已经刷新到磁盘。当恢复任何没有被刷新到磁盘的日志片段时,Kafka会检查每条消息的CRC来验证完整性并且会重建相关的位移索引文件。


Kafka的持久化不需要同步数据到磁盘,因为失败的结点总是从它的副本恢复数据。


建议使用默认的刷新设置,即完全关闭应用磁盘刷新,依赖操作系统的后台刷新和Kafka自己的后台刷新。



Understanding Linux OS Flush Behavior

在Linux中,数据写入到文件系统之前是在页面缓存中维护的。页面缓存中的数据通过应用层的fsync或者操作系统层自己的刷新策略写入到磁盘。数据刷新到磁盘是通过一组线程叫做pdflush来完成的。


pdflush线程有一个可以配置的刷新磁盘策略用来控制在页面缓存中可以维护多少脏数据和多长时间以后应该将这些数据写回到磁盘。当pdflush线程无法赶上数据写入磁盘的速率时,它会最终导致写操作的延迟。


> cat /proc/meminfo

可以通过这个文件来查看当前的内存状态


使用页面缓存比使用进程内的缓存来存储需要写入到磁盘的数据有以下几点好处:

 I/O调度器会将顺序的小的写操作批处理成更大的物理磁盘的写操作,这样可以改善吞吐量

 I/O调度器会试图对这些写操作重新排序以最小程度地移动磁头来改善吞吐量

 它自动地使用机器上的所有空闲内存



Filesystem Selection

Kafka使用磁盘上的普通文件,所以没有强硬依赖特殊的文件系统。使用最多的两种文件系统就是EXT4和XFS。基于历史原因,EXT4比XFS应用得更多,但是近期对XFS文件系统的改进表明使用XFS文件系统对Kafa集群又更高的性能。


通过在不同文件系统上对Kafka集群性能进行对比测试,Kafka的首要性能参数"Request Local Time"在XFS上为160ms,对比EXT4上为250ms。对比测试的时候,文件系统都作了最优调整。


General Filesystem Notes

任何用Kafka数据存储的Linux文件系统,建议使用以下挂载参数:

  noatime    

     这个参数用于关闭文件被读取时更新它的atime属性。Kafka不依赖文件系统的atime属性,所以可以将它关闭


XFS Notes

 largeio

     This affects the preferred I/O size reported by the stat call.While this can allow for higher performance on larger disk writes,in practice it had minimal or no effect on performance.


 nobarrier

     For underlying devices that have battery-backed cache, this option can provide a little more performance by disabling periodic write flushes. However, if the underlying device is well-behaved, it will report to the filesystem that it does not require flushes, and this option will have no effect.


EXT4 Notes

以下调整的参数不是很安全,如果对于单个broker结点挂掉,调整了不是问题,但是对于多个broker结点挂掉,数据不是很容易恢复


  data=writeback   

      EXT4默认使用data=ordered,对于某些写操作按照严格顺序执行。Kafka不需要这种排序,因为它会对所有没有被刷新到磁盘的日志作数据恢复操作。设置这个参数移除排序限制,可以明显减少延迟。

  Disabling journaling 关闭文件系统日志记录功能

      文件系统的日志记录功能是一个折衷方案。它使服务器在崩溃后启动更快,但是也会由于增加额外的锁机制进而影响写操作性能。如果不关心启动时间和想要降低写操作延迟可以关闭这个完全地关闭日志记录功能


  commit=num_secs

      这个参数调整ext4文件系统提交中间数据日志的频率。设置较小的值可以减少当服务器崩溃时未被刷新到磁盘的数据丢失,设置较大的值可以增加吞吐量


  nobh

      当使用data=writeback时,这个设置控制额外的排序保证。这个参数对于Kafka是安全的,因为Kafka不依赖写操作的排序,这样可以提高吞吐量和减少延迟



 delalloc  

     delaloc的意思是 delayed allocation 延迟分配(磁盘块)意味着文件系统避免分配任何磁盘块直到物理写操作出现。这样可以允许EXT4文件系统分配一个大点的extent替代更小的页面,并且帮助确认数据是被顺序写入到磁盘上的。这个参数对于提高吞吐量有很大帮助。

  

 

  




 










参考文档:

http://kafka.apache.org/

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

http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html

https://en.wikipedia.org/wiki/Bandwidth-delay_product

https://en.wikipedia.org/wiki/Page_cache