Kafka实战笔记

单机版搭建

kafka的运行需要提前配好Java 环境,笔者的是 java version “1.8.0_201”

第一步 下载程序

下载源码 此处用的是2.11版本

解压

[root@cluster01:opt] # tar -xzf kafka_2.11-2.1.0.tgz
[root@cluster01:opt] # cd kafka_2.11-2.1.0

第二步 启动服务

开启zookeeper, kafka的运行需要zookeeper

[root@cluster01:kafka_2.11-2.1.0] # /opt/Apache/zookeeper-3.4.10/bin/zkServer.sh start
或者可以使用kafka自带的zookeeper
[root@cluster01:kafka_2.11-2.1.0] # bin/zookeeper-server-start.sh config/zookeeper.properties

运行kafka程序

[root@cluster01:kafka_2.11-2.1.0] # bin/kafka-server-start.sh config/server.properties

KAFKA权威指南 第2版pdf_环境搭建

第三步 创建Topic

创建名为 test3 且分区数为1,重复因子为1的 topic3

[root@cluster01:kafka_2.11-2.1.0] # bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test3
Created topic "test3".

KAFKA权威指南 第2版pdf_KAFKA权威指南 第2版pdf_02

查看创建的 topic

[root@cluster01:kafka_2.11-2.1.0] # bin/kafka-topics.sh --list --zookeeper localhost:2181
KAFKA-STORM
__consumer_offsets
connect-test
first
test
test2

第四步 发送消息

只用kafka自带的生产者脚本发送消息

[root@cluster02:kafka_2.11-2.1.0] # bin/kafka-console-producer.sh --broker-list cluster02:9092 --topic test
>hello
>hu

KAFKA权威指南 第2版pdf_实战_03

第五步 消费消息

使用kafka自带的消费者脚本消费消息

[root@cluster02:kafka_2.11-2.1.0] # bin/kafka-console-consumer.sh --bootstrap-server   cluster02:9092 --topic test --from-beginning
hello
hu

note : 应当使用 --bootstrap-server 参数,之前的版本可能使用 --zookeeper , --from-beginning 参数指明从最初的消息开始读取(消费)

KAFKA权威指南 第2版pdf_实战_04

至此,单机版 kafka 已经尝鲜完毕,总结就是下载程序安装,开启zookeeper,kafka,创建topic,生产消息,消费消息

集群版搭建

这里的集群环境是三台CentOS 7,VMware虚拟机

KAFKA权威指南 第2版pdf_KAFKA权威指南 第2版pdf_05

第一步 下载程序

下载源码 此处用的是2.11版本
在三台CentOS 7服务器中分别下载好kafka程序包,如不想使用自带的zookeeper可以自己下载安装,笔者的三台服务器中之前已经安装好了

解压

[root@cluster01:opt] # tar -xzf kafka_2.11-2.1.0.tgz
[root@cluster01:opt] # cd kafka_2.11-2.1.0

第二步 修改主机名【可跳过】

登录主机之后使用 hostname 命令修改主机名,这样好区分三台主机,当然,不修改也无伤大雅

# 第一台
[root@localhost:~] # hostname cluster01
# 第二台
[root@localhost:~] # hostname cluster02
# 第三台
[root@localhost:~] # hostname cluster03

配置 hosts

为了不每次指定的服务器的时候都使用 IP 指定,可以在这三台服务器和外部主机的 hosts 文件中指定名称

笔者的外部主机使用的是 Ubuntu 18.10

首先使用 ifconfig/ ip address 命令查看CentOS7服务器的 IP 地址

KAFKA权威指南 第2版pdf_kafka_06

然后修改各自的 hosts 文件

[root@sairoPC:sairo] # vim /etc/hosts
......
192.168.67.129  cluster01
192.168.67.130  cluster02
192.168.67.131  cluster03
......

另外CentOS7自带的tty终端界面感觉有些丑陋,Ubuntu的用户可以使用 ssh 远程连接命令,或者使用 Terminus 远程连接工具,Windows 的用户可以尝试 XShell , XFtp

因为我们的虚拟机的IP是根据DHCP协议动态分配的,所以有的时候IP肯能会自动改变,这个是可以:

  • 修改hosts文件
  • 固定服务器的IP分配策略,使其静态分配,具体方法可参考笔者的另一篇博客

第三步 修改配置文件

kafka集群搭建必须要修改以下三项

broker.id=1	# 必须唯一标识broker
 listeners=listeners = listener_name://host_name:port
 log.dirs=/tmp/kafka-logs-1		# 日志目录

笔者的修改如下

# The id of the broker. This must be set to a unique integer for each broker.
broker.id=1		# 修改,各个服务器中的数值必须不同以标识broker

########################## enable delete topic ###########################
delete.topic.enable=true 		# 添加此项
......
# The address the socket server listens on. It will get the value returned from 
# java.net.InetAddress.getCanonicalHostName() if not configured.
#   FORMAT:
#     listeners = listener_name://host_name:port
#   EXAMPLE:
#     listeners = PLAINTEXT://your.host.name:9092
listeners=PLAINTEXT://cluster01:9092	# 添加此项,此处的cluster01对应于具体的主机名
......
# A comma separated list of directories under which to store log files
log.dirs=/opt/Apache/kafka_2.11-2.1.0/logs	# 修改此项,注意这个logs 目录事先不存在,需自行创建,也可以随意指定
......
# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=cluster01:2181,cluster02:2181,cluster03:2181	# 指定zookeeper,cluster*对应hosts文件中ip对应																																		# 的名称,读者按需修改

至此,kafka的集群的搭建已经完成,需要提醒的是kafka集群的运行需要zookeeper协调, 至于zookeeper集群的搭建读者可以谷歌搜索相关资料。或者直接使用kafka自带的zookeeper插件,不用再搭建zookeeper集群。笔者有时间也会再写一篇zookeeper集群环境搭建的文章。

第四步 启动kafka

进入到kafka目录

[root@cluster02:~] # cd /opt/Apache/kafka_2.11-2.1.0/
[root@cluster02:kafka_2.11-2.1.0] # ls
bin  config  libs  LICENSE  logs  NOTICE  site-docs

KAFKA权威指南 第2版pdf_生产者消费者_07

以下的操作在三台服务器中是相同的,节省篇幅只列出一台操作过程,三台服务器的操作不分先后

先启动zookeeper, 这里使用的是非内置的zookeeper,

[root@cluster03:kafka_2.11-2.1.0] # /opt/Apache/zookeeper-3.4.10/bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /opt/Apache/zookeeper-3.4.10/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@cluster03:kafka_2.11-2.1.0] # /opt/Apache/zookeeper-3.4.10/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/Apache/zookeeper-3.4.10/bin/../conf/zoo.cfg
Mode: follower
[root@cluster03:kafka_2.11-2.1.0] #

在命令行执行如下命令以启动kafka

[root@cluster02:kafka_2.11-2.1.0] # bin/kafka-server-start.sh config/server.properties 
[2019-02-26 21:58:44,997] INFO Registered kafka:type=kafka.Log4jController MBean (kafka.utils.Log4jControllerRegistration$)
[2019-02-26 21:58:45,390] INFO starting (kafka.server.KafkaServer)
[2019-02-26 21:58:45,391] INFO Connecting to zookeeper on cluster01:2181,cluster02:2181,cluster03:2181 (kafka.server.KafkaServer)
......

请注意,启动kafka的脚本是kafka-server-start.sh,参数是配置文件 server.properties(这个文件名不是固定的) ,所以每一个配置文件对应于一个kafka的broker, 所以读者如果想在一台服务器上搭建多个kafka节点,可以创建多个配置文件,修改其中的配置参数,然后用kafka-server-start.sh脚本启动,至于如何修改请参考第三步和官方教程

第五步 创建一个topic

[root@cluster01:kafka_2.11-2.1.0] # bin/kafka-topics.sh --create --zookeeper cluster01:2181 --replication-factor 3 --partitions 1 --topic test4
Created topic "test4".
[root@cluster01:kafka_2.11-2.1.0] # bin/kafka-topics.sh --list --zookeeper cluster01:2181
KAFKA-STORM
__consumer_offsets
connect-test
first
test
test2
test3
test4
[root@cluster01:kafka_2.11-2.1.0] #

第六步 启动生产者

[root@cluster01:kafka_2.11-2.1.0] # bin/kafka-console-producer.sh --broker-list cluster01:9092 --topic test4
>1
>2
>
>3
>

第七步 启动消费者

在另一台机器上

[root@cluster02:kafka_2.11-2.1.0] # bin/kafka-console-consumer.sh --bootstrap-server cluster02:9092 --from-beginning --topic test4
1
2

3

看,集群的效果出来了

以上的演示是kafka从生产者生产消息然后消费消息,但实际上消息的来源是多方面的,可以从数据库获取,本地文件获取。kafka本身也提供一个工具 connect-standalone.sh,有兴趣的读者可以看看,其官网有提供示例

编程案例

接下来我们用代码实现生产者生产消息,消费者消费消息

KafkaProducer API

KafkaProducer API的核心部分是KafkaProducer类。KafkaProducer类提供了一个选项,用于在其构造函数中使用以下方法连接Kafka broker。

  • KafkaProducer类提供send方法以异步方式topic发送消息。send() 的签名如下
producer.send(new ProducerRecord<byte[],byte[]>(topic, partition, key1, value1) , callback);
  • ProducerRecord - 生产者管理等待发送的记录缓冲区。
  • Callback - 用户提供的回调,在服务器确认记录时执行(null表示没有回调)。
  • KafkaProducer类提供了一个flush方法,以确保所有先前发送的消息都已实际完成。flush方法的签名如下:
public void flush()
  • KafkaProducer类提供partitionFor方法,该方法有助于获取给定主题的分区元数据。这可以用于自定义分区。这种方法的签名如下:
public Map metrics()

它返回生产者维护的内部metrics的映射。

public void close() - KafkaProducer类提供关闭方法块,直到完成所有先前发送的请求。

Producer API

Producer API的核心部分是Producer类。Producer类提供了一个通过以下方法在其构造函数中连接Kafka broker 的选项。

The Producer Class

生产者类提供send方法,使用以下方法将消息发送到单个或多个主题。

public void send(KeyedMessaget<k,v> message) 
//sends the data to a single topic,par-titioned by key using either sync or async producer.
public void send(List<KeyedMessage<k,v>>messages)
//sends data to multiple topics.
Properties prop = new Properties();
prop.put(producer.type,”async”)
ProducerConfig config = new ProducerConfig(prop);

生产者有两种类型 —“同步”和“异步”

同样的API配置也适用于Sync生产者。它们之间的区别是同步型生产者直接发送消息,但是在后台发送。当您需要更高的吞吐量时,首选异步生成器。在0.8之前的版本中,异步生产者没有*send()*的回调来注册错误处理程序。这仅在当前版本的0.9中可用。

public void close()

Producer类提供close方法来关闭与所有Kafka broker 的生产者池连接。

Configuration Settings

下表列出了Producer API的主要配置设置,以便更好地了解

S.No

Configuration Settings and Description

1

client.id

2

producer.type either sync or async

3

acks The acks config controls the criteria under producer requests are con-sidered complete.

4

retries If producer request fails, then automatically retry with specific value.

5

bootstrap.servers bootstrapping list of brokers.

6

linger.ms if you want to reduce the number of requests you can set linger.ms to something greater than some value.

7

key.serializer Key for the serializer interface.

8

value.serializer value for the serializer interface.

9

batch.size Buffer size.

10

buffer.memory controls the total amount of memory available to the producer for buff-ering.

ProducerRecord API

ProducerRecord是一个键/值对,发送到Kafka cluster。ProducerRecord用于构造包含分区,键和值对的记录。

public ProducerRecord (string topic, int partition, k key, v value)
  • Topic − 用户定义的主题名称将附加到记录。
  • Partition − 分区计数
  • Key − 将包含在记录中的key
  • Value − 记录内容
public ProducerRecord (string topic, k key, v value)

ProducerRecord类构造函数用于创建具有键/值对且没有分区的记录。

  • Topic − 创建 topic 以分配记录。
  • Key − 记录的 key
  • Value − 记录内容
public ProducerRecord (string topic, v value)
  • Topic − 创建主题
  • Value − 记录内容

ProducerRecord类方法列表

S.No

Class Methods and Description

1

public string topic() Topic will append to the record.

2

public K key() Key that will be included in the record. If no such key, null will be returned here.

3

public V value() Record contents.

4

partition() Partition count for the record

截至目前,我们已经创建了一个生产者来向Kafka集群发送消息。现在让我们创建一个消费者来消费Kafka集群中的消息。KafkaConsumer API用于使用来自Kafka群集的消息。KafkaConsumer类构造函数定义如下:

public KafkaConsumer(java.util.Map<java.lang.String,java.lang.Object> configs)
  • configs - 返回消费者配置图。

KafkaConsumer

KafkaConsumer类具有以下重要方法,如下表所示:

S.No

Method and Description

1

public java.util.Set assignment() Get the set of partitions currently assigned by the con-sumer.

2

public string subscription() Subscribe to the given list of topics to get dynamically as-signed partitions.

3

public void subscribe(java.util.List<java.lang.String> topics, ConsumerRebalanceListener listener) Subscribe to the given list of topics to get dynamically as-signed partitions.

4

public void unsubscribe() Unsubscribe the topics from the given list of partitions.

5

public void subscribe(java.util.List<java.lang.String> topics) Subscribe to the given list of topics to get dynamically as-signed partitions. If the given list of topics is empty, it is treated the same as unsubscribe().

6

public void subscribe(java.util.regex.Pattern pattern, ConsumerRebalanceLis-tener listener) The argument pattern refers to the subscribing pattern in the format of regular expression and the listener argument gets notifications from the subscribing pattern.

7

public void assign(java.util.List partitions) Manually assign a list of partitions to the customer.

8

poll() Fetch data for the topics or partitions specified using one of the subscribe/assign APIs. This will return error, if the topics are not subscribed before the polling for data.

9

public void commitSync() Commit offsets returned on the last poll() for all the sub-scribed list of topics and partitions. The same operation is applied to commitAsyn().

10

public void seek(TopicPartition partition, long offset) Fetch the current offset value that consumer will use on the next poll() method.

11

public void resume() Resume the paused partitions.

12

public void wakeup() Wakeup the consumer.

ConsumerRecord API

ConsumerRecord API用于接收来自Kafka群集的记录。此API包含主题名称,分区号,从中接收记录以及指向Kafka分区中记录的偏移量。ConsumerRecord类用于创建具有特定主题名称,分区计数和<键,值>对的使用者记录。它有以下签名:

public ConsumerRecord(string topic,int partition, long offset,K key, V value)
  • Topic - 从Kafka群集收到的使用者记录的主题名称。
  • Partition - 主题的分区。
  • Key - 记录的密钥,如果没有key,则返回null。
  • Value - 记录内容。

ConsumerRecords API

ConsumerRecords API充当ConsumerRecord的容器。此API用于保留特定主题的每个分区的ConsumerRecord列表。其构造函数定义如下。

public ConsumerRecords(java.util.Map<TopicPartition,java.util.List<ConsumerRecord>K,V>>> records)
  • TopicPartition - 返回特定主题的分区映射。
  • Records = ConsumerRecord的返回列表。

ConsumerRecords类定义了以下方法:

S.No

Methods and Description

1

public int count() The number of records for all the topics.

2

public Set partitions() The set of partitions with data in this record set (if no data was returned then the set is empty).

3

public Iterator iterator() Iterator enables you to cycle through a collection, obtaining or re-moving elements.

4

public List records() Get list of records for the given partition.

Configuration Settings

S.No

Settings and Description

1

bootstrap.servers Bootstrapping list of brokers.

2

group.id

3

enable.auto.commit Enable auto commit for offsets if the value is true, otherwise not committed.

4

auto.commit.interval.ms

5

session.timeout.ms

代码

以下代码已经放到 github https://github.com/lymboy/kafka1.git

在开始编码之前,先在虚拟机中启动 zookeeper 和创建一个 topic

笔者使用的 IDEA, 新建是 maven 项目,

<dependencies>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka_2.12</artifactId>
        <version>2.1.0</version>
    </dependency>
</dependencies>

生产者

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

/**
 * @author sairo
 * @date 2019-2-21
 */
public class MyKafkaProducer {

    public static void main(String[] args) {
        Properties props = new Properties();
        //设置服务器地址
        props.put("bootstrap.servers", "cluster01:9092");
        //设置对所有请求应答
        props.put("acks", "all");
        //如果请求失败,kafka可以自动重试
        props.put("retries", 0);
        //设置缓冲区大小
        props.put("batch.size", 16384);
        //减少小于0的请求数
        props.put("linger.ms", 1);
        //buffer.memory控制生产者可用于缓冲的总内存量。
        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<>(props);
        for(int i = 0; i < 10; i++)
            producer.send(new ProducerRecord<>("test4",
                    Integer.toString(i), Integer.toString(i)));
        System.out.println("Message sent successfully");
        producer.close();
    }
}

启动运行后,使用消费者脚本查看

KAFKA权威指南 第2版pdf_实战_08

KAFKA权威指南 第2版pdf_kafka_09

消费者

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

import java.util.Arrays;
import java.util.Properties;

/**
 * @author sairo
 * @date 2019-02-21
 */
public class MyKafkaConsumer {

    public static void main(String[] args) {
        //Kafka consumer configuration settings
        String topicName = "test4";
        Properties props = new Properties();

        props.put("bootstrap.servers", "cluster01:9092");
        props.put("group.id", "test-consumer-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<>(props);

        //Kafka Consumer subscribes list of topics here.
        consumer.subscribe(Arrays.asList(topicName));

        //print the topic name
        System.out.println("Subscribed to topic " + topicName);
        int i = 0;

        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records)

                // print the offset,key and value for the consumer records.
                System.out.printf("offset = %d, key = %s, value = %s\n",
                        record.offset(), record.key(), record.value());
        }
    }
}

运行后,生产者使用名为 test4topic 发送一些简单的消息

KAFKA权威指南 第2版pdf_实战_10

项目总览

KAFKA权威指南 第2版pdf_环境搭建_11