1、安装前的环境准备
由于Kafka是用Scala语言开发的,运行在JVM上,因此在安装Kafka之前需要先安装JDK。
yum install java-1.8.0-openjdk* -y
kafka严重依赖zookeeper,所以需要先安装zookeeper
wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz
tar -zxvf zookeeper-3.4.12.tar.gz
cd zookeeper-3.4.12
cp conf/zoo_sample.cfg conf/zoo.cfg
启动zookeeper
bin/zkServer.sh start conf/zoo.cfg &
bin/zkCli.sh
查看zk的根目录相关节点
ls /
2、下载安装包
下载1.1.0 release版本,并解压:
wget http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/1.1.0/kafka_2.11-1.1.0.tgz
tar -xzf kafka_2.11-1.1.0.tgz
cd kafka_2.11-1.1.0
3、启动服务
现在来启动kafka服务:
启动脚本语法:
kafka-server-start.sh [-daemon] server.properties
可以看到,server.properties的配置路径是一个强制的参数,-daemon表示以后台进程运行,否则ssh客户端退出后,就会停止服务。(注意,在启动kafka时会使用linux主机名关联的ip地址,所以需要把主机名配置到本地host里,用vim /etc/hosts)
\bin/kafka-server-start.sh -daemon config/server.properties
我们进入zookeeper目录通过zookeeper客户端查看下zookeeper的目录树
bin/zkCli.sh
查看zk的根目录kafka相关节点
ls /
查看kafka节点
ls /brokers/ids
4、创建主题
现在我们来创建一个名字为“test”的Topic,这个topic只有一个partition,并且备份因子也设置为1:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
现在我们可以通过以下命令来查看kafka中目前存在的topic
bin/kafka-topics.sh --list --zookeeper localhost:2181
除了我们通过手工的方式创建Topic,我们可以配置broker,当producer发布一个消息某个指定的Topic,但是这个Topic并不存在时,就自动创建。
5、发送消息
kafka自带了一个producer命令客户端,可以从本地文件中读取内容,或者我们也可以以命令行中直接输入内容,并将这些内容以消息的形式发送到kafka集群中。在默认情况下,每一个行会被当做成一个独立的消息。首先我们要运行发布消息的脚本,然后在命令中输入要发送的消息的内容:
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
>this is a msg
>this is a another msg
6、消费消息
对于consumer,kafka同样也携带了一个命令行客户端,会将获取到内容在命令中进行输出:
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning
如果你是通过不同的终端窗口来运行以上的命令,你将会看到在producer终端输入的内容,很快就会在consumer的终端窗口上显示出来。
以上所有的命令都有一些附加的选项;当我们不携带任何参数运行命令的时候,将会显示出这个命令的详细用法。
还有一些其他命令如下:
查看组名
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
--new-consumer
查看消费者的消费偏移量
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092
--describe --group testGroup
消费多主题
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092
--whitelist "test|test-2"
单播消费
一条消息只能被某一个消费者消费的模式,类似queue模式,只需让所有消费者在同一个消费组里即可,分别在两个客户端执行如下消费命令,然后往主题里发送消息,结果只有一个客户端能收到消息
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092
--consumer-property group.id=testGroup --topic test
多播消费
一条消息能被多个消费者消费的模式,类似public-subscribe密室,针对卡夫卡同一条消息只能被同一个消费组下的某一个消费者消费的特性,要实现多播只要保证这些消费者属于不同的消费组即可。我们再增加一个消费者,改消费者属于testGroup-2消费组,结果两个客户端都能收到消息
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092
--consumer-property group.id=testGroup-2 --topic test
7、kafka集群配置(伪分布式)
到目前为止,我们都是在一个单节点上运行broker,这并没有什么意思。对于kafka来说,一个单独的broker意味着kafka集群中只有一个节点。
要想增加kafka集群中的节点数量,只需要多启动几个broker实例即可。
为了有更好的理解,现在我们在一台机器上同时启动三个broker实例。
首先,我们需要建立好其他2个broker的配置文件:
cp config/server.properties config/server-1.properties
cp config/server.properties config/server-2.properties
配置文件的内容分别如下:
config/server-1.properties:
broker.id=1
listeners=PLAINTEXT://:9093
log.dir=/tmp/kafka-logs-1
config/server-2.properties:
broker.id=2
listeners=PLAINTEXT://:9094
log.dir=/tmp/kafka-logs-2
其中,broker.id属性在kafka集群中必须要是唯一的。我们需要重新指定port和log目录,因为我们是在同一台机器上运行多个实例。
如果不进行修改的话,consumer只能获取到一个instance实例的信息,或者是相互之间的数据会被影响。目前我们已经有一个zookeeper实例和一个broker实例在运行了,现在我们只需要在启动2个broker实例即可:
bin/kafka-server-start.sh config/server-1.properties &
bin/kafka-server-start.sh config/server-2.properties &
现在我们创建一个新的topic,备份因子设置为3:
bin/kafka-topics.sh --create --zookeeper localhost:2181
--replication-factor 3 --partitions 1 --topic my-replicated-topic
现在我们已经有了集群,并且创建了一个3个备份因子的topic,但是到底是哪一个broker在为这个topic提供服务呢,因为我们只有一个分区,所以肯定同时只有一个broker在处理这个topic?
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
第一行是所有分区的概要信息,之后的每一行表示每一个partition的信息。因为目前我们只有一个partition,因此关于partition的信息只有一行。
leader节点:负责给定partition的所有读写请求。如果一个topic有多个
partitions,那么每个节点都会其中一部分partition的leader。
在zookeeper上查看partition leader
replicas : 表示某个partition在哪几个broker上存在备份。不管这个几点是不是”leader“,甚至这个节点挂了,也会列出。
isr :是replicas的一个子集,它只列出当前还存活着的,并且备份了该partition的节点。
现在,0号节点是leader,即使用server.properties启动的那个进程。
我们可以运行相同的命令查看之前创建的名称为”test“的topic
# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
没有什么值得惊讶的地方,我们之前设置了topic的partition数量为1,备份因子为1,因此显示就如上所示了;
现在我们向新建的topic中发送一些message:
# bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-replicated-topic
>my test msg 1
>my test msg 2
现在开始消费:
# bin/kafka-console-consumer.sh --zookeeper localhost:2181 --from-beginning --topic my-replicated-topic
my test msg 1
my test msg 2
现在我们来测试我们容错性,因为broker0目前是leader,所以我们要将其kill
# ps -ef | grep server.properties
# kill -9 1177
现在再执行命令:
# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
我们可以看到,leader节点已经变成了broker 2.要注意的是,在Isr中,已经没有了0号节点。leader的选举也是从ISR中进行的。
此时,我们依然可以 消费新消息:
# bin/kafka-console-consumer.sh --zookeeper localhost:2181 --from-beginning --topic my-replicated-topic
my test msg 1
my test msg 2
8、JavaAPI
pom.xmi文件
4.0.0
com.xxx.kafka
kafkaDemo
0.0.1-SNAPSHOT
jar
kafkaDemo
http://maven.apache.org
UTF-8
org.apache.kafka
kafka-clients
1.1.0
ch.qos.logback
logback-core
1.1.3
ch.qos.logback
logback-classic
1.1.1
junit
junit
3.8.1
test
Producer文件
package com.xxx.kafka.kafkaDemo;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
public class MsgProducer {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.0.60:9092,192.168.0.60:9093");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer producer = new KafkaProducer<>(props);
for (int i = 0; i < 5; i++) {
//同步方式发送消息
ProducerRecord producerRecord =
new ProducerRecord("test", 0, Integer.toString(i), Integer.toString(i));
/* 使用send是先发送到缓存区了,然后就立刻返回*/
/*Future result = producer.send(producerRecord);
//等待消息发送成功(真正发送到broker节点上)的同步阻塞方法
RecordMetadata metadata = result.get();
System.out.println("同步方式发送消息结果:" + "topic-" + metadata.topic()
+ "|partition-"+ metadata.partition() + "|offset-" + metadata.offset());*/
//异步方式发送消息
producer.send(producerRecord, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception != null) {
System.err.println("发送消息失败:" + exception.getStackTrace());
}
if (metadata != null) {
System.out.println("异步方式发送消息结果:"
+ "topic-" + metadata.topic()
+ "|partition-" + metadata.partition()
+ "|offset-" + metadata.offset());
}
}
});
}
producer.close();
}
}
Consumer文件
package com.xxx.kafka.kafkaDemo;
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.common.TopicPartition;
public class MsgConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.0.60:9092");
// 消费分组名
props.put("group.id", "testGroup");
// 是否自动提交offset
//props.put("enable.auto.commit", "true");
// 自动提交offset的间隔时间
//props.put("auto.commit.interval.ms", "1000");
props.put("enable.auto.commit", "false");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer consumer = new KafkaConsumer<>(props);
// 消费主题
consumer.subscribe(Arrays.asList("test"));
// 消费指定分区
//consumer.assign(Arrays.asList(new TopicPartition("test", 0)));
while (true) {
/*
* poll() API 主要是判断consumer是否还活着,只要我们持续调用poll(),
*消费者就会存活在自己所在的group中,
* 并且持续的消费指定partition的消息。底层是这么做的:
*消费者向server持续发送心跳,如果一个时间段(session.timeout.ms)
*consumer挂掉或是不能发送心跳,这个消费者会被认为是挂掉了,
* 这个Partition也会被重新分配给其他consumer(rebalance)
*/
ConsumerRecords records = consumer.poll(1000);
for (ConsumerRecord record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
//手动提交offset
if (records.count() > 0) {
// 提交offset
consumer.commitSync();
}
}
}
}