本文主要对阿里云部署单机版的Kafka做一个简单总结,在阿里云上部署一个简单的Kafka服务可以方便学习或者在开发过程中的一些测试。
一、部署环境
- CPU:1核
- 内存:2 GiB
- 实例类型:I/O优化
- 操作系统:CentOS Linux release 7.7.1908
- JDK:1.8.0_212
- Kafka:kafka_2.13-2.4.0
二、安装JDK
这里简单过一下JDK的安装,下载上传这些步骤省略。
1.卸载现有JDK
有些系统自带有OpenJDK,或者不想用旧版本JDK,需要先卸载再安装,先输入以下命令查看现有JDK:
rpm -qa|grep jdk
卸载现有JDK:
rpm -e --nodeps 现有jdk版本信息
卸载完了再次输入rpm -qa|grep jdk
查看是否卸载成功
2.解压JDK安装包
解压JDK安装包:
tar -zxvf jdk-8u212-linux-x64.tar.gz
3.配置环境变量
vim打开profile
文件:
vim /etc/profile
末尾添加以下内容,JAVA_HOME
配置JDK安装路径:
export JAVA_HOME=/usr/local/jdk/jdk1.8.0_212
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
刷新配置,使环境变量生效:
source /etc/profile
5.验证JDK是否安装成功
重启服务器后查看JDK版本信息:
java -version
JDK安装成功:
三、安装Kafka
这里使用的是Kafka自带的Zookeeper,所以没安装Zookeeper,用于简单的测试这种方式已经足够了。在安装之前,需要给阿里云服务实例添加一个下图所示的安全组,本地放行全部端口以防止外网远程连接Kafka时失败:
1.Kafka下载
进入Kafka官方下载,这里下载的下图红框中的版本:
2.上传安装包至服务器
这里用的MobaXterm自带的sftp工具直接拖动上传,本例安装至/usr/local/kafka/
:
3.解压Kafka安装包
进入/usr/local/kafka/
目录,解压Kafka安装包,解压后的文件夹名为kafka_2.13-2.4.0
:
tar -zxvf kafka_2.13-2.4.0.tgz
4.修改配置
vim打开server.properties
配置文件:
vim kafka_2.13-2.4.0/config/server.properties
在server.properties
里进行如下配置:
# broker唯一标识
broker.id=1
# kafka日志目录
log.dirs=/usr/local/kafka/kafka_2.13-2.4.0_logs
# 启用删除topic,如果此配置已关闭,通过管理工具删除topic将没有任何效果
delete.topic.enable=true
# 停用自动创建topic
auto.create.topics.enable=false
# kafka连接地址
listeners=PLAINTEXT://阿里云私网ip:9092(本地访问用本地ip)
# 如果要提供外网访问则必须配置此项
advertised.listeners=PLAINTEXT://阿里云公网ip:9092(若要远程访问阿里云需配置此项为阿里云的公网ip)
# zookeeper连接地址,集群配置格式为ip:port,ip:port,ip:port
zookeeper.connect=阿里云公网ip:2181
vim打开consumer.properties
:
vim kafka_2.13-2.4.0/config/consumer.properties
配置消费组:
# 此消费者所属消费者组的唯一标识。如果消费者用于订阅或offset管理策略的组管理功能,则此属性是必须的
group.id=test-group-1
5.启动服务
先进入/usr/local/kafka/kafka_2.13-2.4.0/bin/
目录下,后台启动Zookeeper:
./zookeeper-server-start.sh ../config/zookeeper.properties 1>/dev/null 2>&1 &
启动kafka:
./kafka-server-start.sh ../config/server.properties &
检查Zookeeper和kafka是否启动:
netstat -tunlp|egrep "(2181|9092)"
Zookeeper和kafka启动成功:
四、Kafka命令行客户端连接测试
Kafka自带一个命令行客户端,它从文件或标准输入中获取输入,并将其作为消息发送到Kafka服务器。默认情况下,每行将作为单独的消息发送。在创建生产者和消费者之前需先创建topic。
1.创建和删除topic
创建一个名为test的topic:
./kafka-topics.sh --create --zookeeper 阿里云公网ip:2181 --config max.message.bytes=12800000 --config flush.messages=1 --replication-factor 1 --partitions 1 --topic test
常用选项:
--create 指定创建topic动作
--zookeeper 指定kafka连接zookeeper的连接url,该值和server.properties文件中的配置项zookeeper.connect一样
--config 指定当前topic上有效的参数值
--replication-factor 指定每个分区的复制因子个数,默认1个
--partitions 指定当前创建的kafka分区数量,默认为1个
--topic 指定新建topic的名称
下图为创建过程:
查看已有topic:
./kafka-topics.sh --list --zookeeper 阿里云公网ip:2181
发现名为test的topic已经创建成功:
想要删除topic需先配置server.properties
文件,给定参数delete.topic.enable=true
,重启kafka服务,此时执行delete命令表示允许进行Topic的删除。由于之前已经配置过了这步就不用做了,直接执行下面的命令删除名为test的topic:
./kafka-topics.sh --delete --topic test --zookeeper 阿里云公网ip:2181
下图为删除topic:
再次查看topic发现topic已删除:
2.发送和接收消息
先创建一个名为test的topic,再创建一个生产者:
./kafka-console-producer.sh --broker-list 阿里云公网ip:9092 --topic test
创建一个消费者,注意不能用以下老版Kafka的--zookeeper 阿里云公网ip:2181
的方式启动,这种方式新版Kafka已经移除:
./kafka-console-consumer.sh --zookeeper 阿里云公网ip:2181 --topic test --from-beginning
用上面的方式创建消费者会报下面的错误:
正确的创建方式是下面这种:
./kafka-console-consumer.sh --bootstrap-server 阿里云公网ip:9092 --topic test --from-beginning
下图为创建了一个生产者并发送了3条消息:
下图为创建了一个消费者,发现已成功接收到消费者发送的3条消息,发送和接收消息成功:
这里生产者的消息要发往kafka,即broker,消费者的消息来自zookeeper的协调转发。
3.查看消息堆积状况
首先查看消费组:
./kafka-consumer-groups.sh --bootstrap-server 阿里云公网ip:9092 --list
可知消费组名为console-consumer-40956:
查看消费堆积情况:
./kafka-consumer-groups.sh --bootstrap-server 阿里云公网ip:9092 --describe --group 消费组名
下图为查看的消息堆积信息:
几个术语解释如下:
CURRENT-OFFSET
:当前消费的位移。LOG-END-OFFSET
:下一条将要被加入到日志的消息的位移。LAG
:消息堆积量,即消息中间件服务端中所留存的消息与消费掉的消息之间的差值,消息堆积量也被称为消费滞后量。
五、Kafka Java客户端连接测试
Kafka客户端的版本最好与Kafka服务端的版本一致,由于Kafka服务端安装的是kafka_2.13-2.4.0
,所以Kafka客户端使用与之对应的kafka-clients-2.4.0
。
1.生产者
package com.rtxtitanv;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.Properties;
/**
* 生产者
*/
public class Producer {
private static Logger logger = LoggerFactory.getLogger(Producer.class.getName());
/**
* 指定topic名称
*/
private static final String TOPIC = "test";
/**
* host/port列表,Kafka服务器ip:端口
* 集群为ip:port,ip:port,ip:port,...,ip:port
*/
private static final String BROKER_LIST = "Kafka服务器ip:9092";
private static KafkaProducer<String, String> producer = null;
/**
* 加载时执行初始化
*/
static {
Properties configs = Producer.initConfig();
producer = new KafkaProducer<String, String>(configs);
}
/**
* 初始化配置
* @return Properties
*/
private static Properties initConfig() {
Properties properties = new Properties();
// host/port列表,用于初始化建立和Kafka集群的连接
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BROKER_LIST);
// key实现org.apache.kafka.common.serialization.Serializer接口的序列化类
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// value实现org.apache.kafka.common.serialization.Serializer接口的序列化类
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 生产者需要leader在确认请求完成之前已收到的反馈数量,这个参数是为了保证发送请求的可靠性
// acks=0:那么生产者将不等待任何消息确认,消息将立刻添加到socket缓冲区并考虑发送
// acks=1:leader节点会将记录写入本地日志并且在所有follower节点反馈之前就先确认成功。
// 在这种情况下,如果leader节点在接收记录之后并且在follower节点复制数据完成之前产生错误,这条记录会丢失。
// acks=all:意味着leader节点会等待所有同步中的副本确认之后再确认这条记录是否发送完成,只要至少有一个同步副本存在,记录就不会丢失。
// 这种方式是对请求传递的最有效保证,acks=-1与acks=all是等效的
properties.put(ProducerConfig.ACKS_CONFIG, "all");
// 设置一个大于0的值将导致客户端重新发送其发送失败并带有潜在临时错误的任何记录
properties.put(ProducerConfig.RETRIES_CONFIG, 0);
// 当将多个消息被发送到同一个分区时,生产者尝试将记录组合到更少的请求中,这有助于提升客户端和服务器端的性能
// 这个配置控制一个批次的默认大小,以字节为单位,默认值为16384
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 生产者会将两个请求发送时间间隔内到达的记录合并到一个单独的批处理请求中,通常只有当记录到达的速度超过了发送的速度时才会出现这种情况
// 然而在某些场景下,即使处于可接受的负载下,客户端也希望能减少请求的数量,这个设置是通过添加少量的人为延迟来实现的
// 也就是说,不是立即发出一个消息,生产者将等待一个给定的延迟,以便和其他的消息可以组合成一个批次,默认值为0,即无延迟
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// 生产者用来缓冲等待被发送到服务器的记录的内存总字节数,默认值为33554432
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
return properties;
}
public static void main(String[] args) throws InterruptedException{
// 消息实体
ProducerRecord<String, String> record = null;
Date start = new Date();
for (int i = 0; i < 16; i++) {
record = new ProducerRecord<>(TOPIC, "value" + (int) (10 * (Math.random())) + "---第" + (i + 1) + "条消息---");
// 发送消息
producer.send(record, (recordMetadata, e) -> {
if (null != e) {
logger.error("send error" + e.getMessage());
} else {
logger.info(String.format("offset:%s", recordMetadata.offset(), recordMetadata.partition()));
}
});
}
Date end = new Date();
logger.info("send OK.used time��"+(end.getTime()-start.getTime()));
producer.close();
}
}
2.消费者
package com.rtxtitanv;
import org.apache.kafka.clients.consumer.ConsumerConfig;
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.serialization.StringDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
/**
* 消费者
*/
public class Consumer {
private static Logger logger = LoggerFactory.getLogger(Consumer.class.getName());
/**
* 指定topic名称
*/
private static final String TOPIC = "test";
/**
* host/port列表,Kafka服务器ip:端口
* 集群为ip:port,ip:port,ip:port,...,ip:port
*/
private static final String BROKER_LIST = "Kafka服务器ip:9092";
private static KafkaConsumer<String, String> consumer =null;
/**
* 加载时执行初始化
*/
static {
Properties configs = Consumer.initConfig();
consumer = new KafkaConsumer<String, String>(configs);
}
/**
* 初始化配置
* @return Properties
*/
private static Properties initConfig() {
Properties properties = new Properties();
// host/port列表,用于初始化建立和Kafka集群的连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BROKER_LIST);
// 此消费者所属消费者组的唯一标识
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test-group");
// key实现org.apache.kafka.common.serialization.Deserializer接口的解析序列化类
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// value实现org.apache.kafka.common.serialization.Deserializer接口的解析序列化类
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 指定了消费者是否自动提交偏移量,默认值为true
properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
// 该属性指定了消费者在读取一个没有偏移量后者偏移量无效的分区的情况下,应该作何处理
// 默认值为latest,即从最新记录读取数据,另一个值earliest,是指在偏移量无效的情况下,消费者从起始位置开始读取数据
properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
return properties;
}
public static void main(String[] args) {
// 接收指定topic的消息
consumer.subscribe(Arrays.asList(TOPIC));
// 接收消息
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> record : records) {
logger.info(record.value().toString());
}
}
}
}
3.发送和接收消息测试
启动消费者和生产者,根据下图可见生产者发送消息成功。
根据下图可见消费者接收消息成功,没有消息丢失和重复,说明通过Kafka Java客户端发送和接收消息成功。