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 = 30000
log.roll.hours = 168
inter.broker.protocol.version = 0.9.0.X
log.preallocate = false
security.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 QuorumPeerMain
928 Kafka
931 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 on
port=9093
# A comma seperated list of directories under which to store log files
log.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 on
port=9094
# A comma seperated list of directories under which to store log files
log.dirs=/tmp/kafka-logs-2
依次启动这些broker。
Broker1
bin/kafka-server-start.sh config/server.properties
Broker2
bin/kafka-server-start.sh config/server-one.properties
Broker3
bin/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 demo
This 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-beginning
This is single node-multi broker demo
This 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