Kafka快速指南

来源:互联网 发布:电脑手动卸载软件 编辑:程序博客网 时间:2024/06/06 00:38

本文中的kafka版本为 kafka_2.12-0.10.2.1

Apache kafka 简介

大数据应用中,涉及到海量的数据,对此我们面对两个主要的挑战。分别是如何收集数据,如何分析数据。为了克服这些挑战,我们需要使用消息发送系统。Kafka用于分布式高吞吐量的系统。Kalfka具有较好的吞吐量适用于大规模消息处理系统。

什么是消息系统?

消息系统负责将数据从一个应用传送到另一个应用中,因此应用程序只需要关注数据本身,而不需要关系如何共享这些数据。分布式消息系统基于可靠的消息队列实现。消息以异步的形式被投递到队列中。有两种消息发送模式,分别是点对点模式,发布订阅模式。大多数消息系统均采用发布订阅模式。

点对点消息系统 :在点对点系统中,消息被持久化到队列中。一个或多个消费者可以消费队列中的消息,但某些特殊的消息最多只能有一个消费者消费。一旦消费者从队列中读取消息,那么这些消息将从队列中删除。下图描述了点对点的结构。

这里写图片描述

发布订阅消息系统:在发布订阅系统中,消息以主题的形式持久化。与点对点系统不同,消费者可以可以订阅一个或多个主题。并可以消费所订阅的主题中的所有消息。在发布订阅系统中,消息的生产者被称为发布者,消息的消费者被称为订阅者。下图描述了发布订阅系统的结构。

这里写图片描述

什么是kafka ?

Apache kafka是一个分布式的基于订阅发布模式的消息系统。并且通过可靠的队列来处理海量数据,并可以将消息从一端传递向另一端。Kafka中的消息被持久化在磁盘上,并在集群中生成多个副本以防止数据丢失。Kafka基于Zookeeper实现。并且与Apache storm,spark等实时流式数据分析工具结合的非常好。此外kafka具备以下特点。

  • 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能。
  • 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输。
  • 支持Kafka Server间的消息分区,及分布式消费,同时保证每个partition内的消息顺序传输。
  • 同时支持离线数据处理和实时数据处理。

使用场景: 系统状态指标数据监控,日志聚合,流式数据处理等。

Apache Kafka基本概念

在学习Kafka前,先要了解一些术语,包括主题(topic),代理(broker),生产者(producer),消费者(consumer)等。
这里写图片描述

主题(topic):某种特殊的消息流被称为主题。主题分为多个分区。每个主题最少有一个分区。每个分区按顺存放消息。分区由一组大小相同的文件组成。

生产者(Producer):就是向kafka broker发消息的客户端。

消费者(Consumer):消息消费者,向kafka broker取消息的客户端。

消费者组(Consumer Group (CG)):若干个消费者构成的集合。这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的方法。一个topic可以有多个CG。topic的消息会复制(不是真的复制,是概念上的)到所有的CG,但每个CG只会把消息发给该CG中的一个consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。

代理(broker):一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。

分区(Partion): topic可能有多个分区,最终分区存放消息数据。为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证按一个partition中的顺序将消息发给consumer,不保证一个topic的整体(多个partition间)的顺序。

偏移(Offset): partition中的每条消息都会被分配一个有序的id(offset)。kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。

分区副本:副本就是分区的备份。副本是为了防止数据丢失。
Kafka集群:多个broker构成Kafka集群。Kafka集群可以在运行时进行扩展。

Leader: Leader负责读写数据,Leader只有一个。

Follower:非Leader结点,接受Leader指令。如果Leader挂掉,follower经过选举后成为新的leader。Follower在集群中充当消费者,从leader拉取数据,并更新自己的数据。

工作流程

发布订阅流程

1.生产者以一定的时间间隔发送某主题的消息。
2.Kafka将消息存储在某个主题的分区中。
3.消费者订阅主题。
4.一旦消费者订阅了一个主题,kafka会将当前offset发送给消费者,并且将偏移量存储在zookeeper集群中。
5.消费者将会以一定的时间间隔发出请求,看有没有新消息。
6.一旦kafka收到生产者的消息,它将转发订阅了这些消息的消费者。
7.消费者接收消息并处理这些消息。
8.一旦kafka接受的确认应答,那么kafka将会更新在zookeeper中维护的offset。
9.上述过程将重复,直到消费者停止请求。
10.费者可以向前选择某些offset或向后选择某些offset,这样跳过某些消息,并请求后续的消息。

Consumer与topic关系、机制及交互流程

每个consumer属于一个consumer group;反过来说,每个group中可以有多个consumer。

对于Topic中的一条特定的消息,只会被订阅此Topic的每个group中的一个consumer消费,此消息不会发送给一个group的多个consumer;那么一个group中所有的consumer将会交错的消费整个Topic。

如果所有的consumer都具有相同的group,这种情况和JMS queue模式很像;消息将会在consumers之间负载均衡。如果所有的consumer都具有不同的group,那这就是”发布-订阅”;消息将会广播给所有的消费者。

在kafka中,一个partition中的消息只会被group中的一个consumer消费(同一时刻);每个group中consumer消息消费互相独立;我们可以认为一个group是一个”订阅”者,一个Topic中的每个partions,只会被一个”订阅者”中的一个consumer消费,不过一个consumer可以同时消费多个partitions中的消息。kafka只能保证一个partition中的消息被某个consumer消费时是顺序的。

事实上从Topic角度来说,当有多个partitions时,消息仍不是全局有序的。通常情况下一个group中会包含多个consumer,这样不仅可以提高topic中消息的并发消费能力,而且还能提高”故障容错”性,如果group中的某个consumer失效,那么其消费的partitions将会有其他consumer自动接管。kafka的设计原理决定,对于一个topic同一个group中不能有多于partitions个数的consumer同时消费,否则将意味着某些consumer将无法得到消息。

Zookeeper的作用

Zookeeper在kafka集群中用来进行配置管理及协调服务。Kafka通过zookeeper集群来共享信息。Kafka将与topic,partition,offset等相关信息存储在zookeeper中。由于重要的数据都存储在zookeeper中,这些数据会在集群中其它结点以副本形式保存,因此kafka代理或zookeeper挂调后都不会影响集群的状态。一旦Zookeeper重启Kafka将会恢复这些状态。此外Kafka集群中的leader选举也是通过zookeeper来实现的。

Apache Kafka 安装步骤

1.安装Java
2.安装Zookeeper
3.安装Apache kafka,进入apache kafka官网下载包。解压后进入bin文件夹下,然后启动服务。如果是windows系统,在进入bin/windows。

通过命令启动停止服务器

$ bin/kafka-server-start.sh config/server.properties

启动后会在屏幕上看到如下的输出

$ bin/kafka-server-start.sh config/server.properties[2016-01-02 15:37:30,410] INFO KafkaConfig values:request.timeout.ms = 30000log.roll.hours = 168inter.broker.protocol.version = 0.9.0.Xlog.preallocate = falsesecurity.inter.broker.protocol = PLAINTEXT…………………………………………….…………………………………………….

停止服务器

$ bin/kafka-server-stop.sh config/server.properties

安装及启动学完了,接着看看kafka的基本操作。

Apache kafka的基本操作

启动服务

启动zookeeper

bin/zookeeper-server-start.sh config/zookeeper.properties

启动kafka代理

bin/kafka-server-start.sh config/server.properties

随后输入jps命令可以看到

821 QuorumPeerMain928 Kafka931 Jps

单节点配置

创建Kafka主题 – 通过Kafka-topics.sh 脚本来在服务器上创建主题。下面的命令完成主题的创建。

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1--partitions 1 --topic topic-name

下面创建了一个名为Hello-Kafka的主题,主题有一个分区,副本因子数为1。

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1   --partitions 1 --topic Hello-Kafka

一旦主题创建后,可以在窗口中得到通知,此外在日志文件中也可以看到。日志位置可以通过server.properties文件中的log.dirs找到。

列出主题 命令列出了所创建的主题

bin/kafka-topics.sh --list --zookeeper localhost:2181

启动生产者发送消息

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic topic-name

命令有两个重要的参数broker-list,topic-name:
broker-list : 我们希希望向哪个代理发送消息。Server.properties文件中配置了代理的端口,默认情况端口为9092。

t:
bin/kafka-console-producer.sh –broker-list localhost:9092 –topic Hello-Kafka
生产者将等待用户输入,并将输入发送到kafka集群中。默认情况,回车结束消息输入。

启动消费者接收消息

消费者的默认配置文件是consumer.proper-ties。

bin/kafka-console-consumer.sh --zookeeper localhost:2181 —topic topic-name--from-beginning

最终生产者推送到消息将会发送给消费者。

集群配置

创建多个brokers :将server.properties赋值两份,分别命名为server-one.properties,server-two.properties,接着修改属性。

server-one.properties改为

# The id of the broker. This must be set to a unique integer for each broker.broker.id=1# The port the socket server listens onport=9093# A comma seperated list of directories under which to store log fileslog.dirs=/tmp/kafka-logs-1

server-two.properties改为

# The id of the broker. This must be set to a unique integer for each broker.broker.id=2# The port the socket server listens onport=9094# A comma seperated list of directories under which to store log fileslog.dirs=/tmp/kafka-logs-2

依次启动这些broker。

Broker1bin/kafka-server-start.sh config/server.propertiesBroker2bin/kafka-server-start.sh config/server-one.propertiesBroker3bin/kafka-server-start.sh config/server-two.properties

随后执行jps可以看到启动的多个代理。

创建主题 : 我们有3个代理,因此将主题的副本因子设置为3。如果有两个代理,那么因子数设置为2即可。

创建主题命令

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3-partitions 1 --topic topic-name

创建名为Multibrokerapplication的主题

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 -partitions 1 --topic Multibrokerapplication

执行后输出

created topic “Multibrokerapplication”

通过Describe命令用来检查监听当前主题的代理是哪个broker,命令如下。
bin/kafka-topics.sh –describe –zookeeper localhost:2181 –topic Multibrokerapplication

执行后的输出如下。

Topic:multibrokerapplication PartitionCount:1 ReplicationFactor:3 Configs:Topic: multibrokerapplication Partition: 0 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0

从输出可以看到,第一行对分区进行了描述,并展示了主题名,分区数,及副本因子数。

第二行中,可以看到当前leader为节点2(Leader: 2),Isr为replicas同步列表(isr),可以看到列表中节点有2,1,0三个,此外只有这个列表里的节点才有可能成为leader。

启动生产者发送消息

bin/kafka-console-producer.sh --broker-list localhost:9092--topic Multibrokerapplication

输入两行消息
This is single node-multi broker demo
This is the second message

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic Multibrokerapplication[2016-01-20 19:27:21,045] WARN Property topic is not valid (kafka.utils.Verifia-bleProperties)This is single node-multi broker demoThis is the second message

启动消费者接收消息

bin/kafka-console-consumer.sh --zookeeper localhost:2181—topic Multibrokerapplica-tion --from-beginning

输出

bin/kafka-console-consumer.sh --zookeeper localhost:2181-topic Multibrokerapplica-tion —from-beginningThis is single node-multi broker demoThis is the second message

主题的修改与删除

修改主题

命令

bin/kafka-topics.sh —zookeeper localhost:2181 --alter --topic topic_name--parti-tions count

例子

We have already created a topic “Hello-Kafka” with single partition count and one replica factor.Now using “alter” command we have changed the partition count.bin/kafka-topics.sh --zookeeper localhost:2181--alter --topic Hello-kafka --parti-tions 2

输出

WARNING: If partitions are increased for a topic that has a key,the partition logic or ordering of the messages will be affectedAdding partitions succeeded!

删除主题

命令

bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic topic_name

例子

bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic Hello-kafka

输出

Topic Hello-kafka marked for deletion

如果配置文件中的delete.topic.enable没有设置为true那么删除命令不会起作用。

生产者与消费者的Java Api操作示例

生产者

package kafka;import java.util.Arrays;import java.util.Properties;import org.apache.kafka.clients.consumer.ConsumerRecord;import org.apache.kafka.clients.consumer.ConsumerRecords;import org.apache.kafka.clients.consumer.KafkaConsumer;import org.apache.kafka.clients.producer.KafkaProducer;import org.apache.kafka.clients.producer.Producer;import org.apache.kafka.clients.producer.ProducerRecord;public class TestProducer {  public static void main(String[] args) {    // 定义主题名称    String topicName = "msg2";    // 创建属性配置对象    Properties props = new Properties();    // localhost    props.put("bootstrap.servers", "localhost:9092");    //对生产者请求进行确认    props.put("acks", "all");    //生产者请求失败,生产者可以重试    props.put("retries", 0);    //指定缓冲区大小    props.put("batch.size", 16384);    //发送间隔的延迟时间    props.put("linger.ms", 1);    //控制缓冲区总内存大小    props.put("buffer.memory", 33554432);    props.put("key.serializer",        "org.apache.kafka.common.serialization.StringSerializer");    props.put("value.serializer",        "org.apache.kafka.common.serialization.StringSerializer");    Producer<String,String> producer = new KafkaProducer<String, String>(props);    for(int i = 0; i < 10; i++) {      producer.send(new ProducerRecord<String, String>(topicName,Integer.toString(i),Integer.toString(i)));      System.out.println("Message sent successfully");    }    producer.close();  }}

消费者

package kafka;import java.util.Arrays;import java.util.Properties;import org.apache.kafka.clients.consumer.ConsumerRecord;import org.apache.kafka.clients.consumer.ConsumerRecords;import org.apache.kafka.clients.consumer.KafkaConsumer;public class TestConsumer {  public static void main(String[] args) {    String topicName = "msg2";    Properties props = new Properties();    props.put("bootstrap.servers", "localhost:9092");    props.put("group.id", "test2");    props.put("enable.auto.commit", "true");    props.put("auto.commit.interval.ms", "1000");    props.put("session.timeout.ms", "30000");    props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");    props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");    KafkaConsumer<String,String> consumer = new KafkaConsumer<String,String>(props);    consumer.subscribe(Arrays.asList(topicName));    System.out.println("Subscribed to topic " + topicName);    while(true) {      ConsumerRecords<String, String> records = consumer.poll(100);      for(ConsumerRecord<String, String> record : records) {        System.out.printf("offset = %d, key = %s, value = %s\n",            record.offset(),record.key(),record.value());      }    }  }}

启动zookeeper,kafka后启动代码可以看到消费者从mq中获取的消息。

消费者组(Consumer Group Api)

消费者可以通过使用同样的group.id来加入一个组。组的最大并发数是组中消费者的数量。

Kafka分配一个partition给组中的一个消费者,因此,每个partition只能被组中的一个消费者消费。Kafka确保消息仅会被消费者组中的一个消费者所读取。

消费者均衡(Rebalancing)算法(Re-balancing of a Consumer)

当一个group中,有consumer加入或者离开时,会触发partitions均衡.均衡的最终目的,是提升topic的并发消费能力.
1) 假如topic1,具有如下partitions: P0,P1,P2,P3
2) 加入group中,有如下consumer: C0,C1
3) 首先根据partition索引号对partitions排序: P0,P1,P2,P3
4) 根据consumer.id排序: C0,C1
5) 计算倍数: M = [P0,P1,P2,P3].size / [C0,C1].size,本例值M=2(向上取整)
6) 然后依次分配partitions: C0 = [P0,P1],C1=[P2,P3],即Ci = [P(i * M),P((i + 1) * M -1)]

注意:启动项目时,记得修改group,将每个消费者分配到不同的组中。

package kafka;import java.util.Arrays;import java.util.Properties;import org.apache.kafka.clients.consumer.ConsumerRecord;import org.apache.kafka.clients.consumer.ConsumerRecords;import org.apache.kafka.clients.consumer.KafkaConsumer;public class ConsumerGroup {  public static void main(String[] args) {    String topic = "msg";    String group = "group2";    Properties props = new Properties();    props.put("bootstrap.servers", "localhost:9092");    props.put("group.id", group);    props.put("enable.auto.commit", "true");    props.put("auto.commit.interval.ms", "1000");    props.put("session.timeout.ms", "30000");    props.put("key.deserializer",        "org.apache.kafka.common.serialization.StringDeserializer");    props.put("value.deserializer",        "org.apache.kafka.common.serialization.StringDeserializer");    KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);    consumer.subscribe(Arrays.asList(topic));    System.out.println("Subscribed to topic " + topic);    while (true) {      ConsumerRecords<String, String> records = consumer.poll(100);      for (ConsumerRecord<String, String> record : records)        System.out.printf("offset = %d, key = %s, value = %s\n",            record.offset(), record.key(), record.value());    }  }}

例如启动上面的代码,消费者1,分配到group中,消费者2,3分配在group2中。然后启动一个生产者,可以观察到同组中只有一个消费者可以接收到消息。

参考:

https://www.tutorialspoint.com/apache_kafka/index.htm