Kafka学习笔记
写在前面
Kafka是一个消息队列框架,在大数据生态体系中有着极其重要的作用。同时其在java后端领域也有着很高的地位。今天就来学习一下kafka
快速入门
前置配置
kafka需要zookeeper的支持,因此要先配置好zookeeper,详见这篇博客: ZooKeeper搭建和使用笔记,这里就默认已经配好了zookeeper
这里选用的是kafka的0.11的版本,如图:
可以从官网进行下载。
配置
kafka的配置还是很简单的。首先我们解压压缩包:
tar -zxvf kafka_2.11-0.11.0.0.tgz -C /opt/module/
在kafka目录下创建data文件夹,用来存储kafka的缓存文件
mkdir data
接着修改配置文件(config/server.properties)
cd config/
vim server.properties
接着修改配置文件:
#broker 的全局唯一编号,不能重复
broker.id=0
#删除 topic 功能使能
delete.topic.enable=true
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘 IO 的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka 运行日志存放的路径
log.dirs=/opt/module/kafka_2.11-0.11.0.0/data
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment 文件保留的最长时间,超时将被删除
log.retention.hours=168
#配置连接 Zookeeper 集群地址
zookeeper.connect=hadoop03:2181,hadoop04:2181,hadoop05:2181
这里要注意,重点要配置的项是broker.id,要全局唯一。我们这里配置的是集群,每台机子的id不能相同。再就是zookeeper集群的配置,这里已经配置了本地host,所以可以直接填写主机名,这里建立都配置一下。
然后如果需要的话,可以配置一下环境变量:
sudo vi /etc/profile
#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka_2.11-0.11.0.0
export PATH=$PATH:$KAFKA_HOME/bin
source /etc/profile
将配置好的kafka包进行分发:
xsync kafka_2.11-0.11.0.0/
关于这里的分发的脚本,可以去看我的这篇博客,里面写了如何写这个分发脚本。
然后在其他的机子上修改一下broker.id
就好了。
启动
要启动的话,十分简单,在kafka目录:
bin/kafka-server-start.sh config/server.properties
如果配置了环境变量,可以直接:
kafka-server-start.sh config/server.properties
然后就发现以上是前台启动
,如果想后台启动
需要加一个参数:
bin/kafka-server-start.sh -daemon config/server.properties
但每次每个机子都要自己启一遍太麻烦了。我们可以写一个脚本来群起kafka:
#!/bin/bash
case $1 in
"start"){
for i in hadoop03 hadoop04 hadoop05
do
echo "*****************$i****************"
ssh $i "/opt/module/kafka_2.11-0.11.0.0/bin/kafka-server-start.sh -daemon /opt/module/kafka_2.11-0.11.0.0/config/server.properties"
done
};;
"stop"){
for i in hadoop03 hadoop04 hadoop05
do
echo "*****************$i****************"
ssh $i "/opt/module/kafka_2.11-0.11.0.0/bin/kafka-server-stop.sh"
done
};;
esac
这里要注意修改主机名,同时注意修改各个文件的位置。该脚本要放在当前用户的bin目录下,如我的就放在了:
然后给它权限:
chmod 777 kk.sh
我们就可以全局使用了:
# 启动
kk.sh start
# 停止
kk.sh stop
命令行使用
kafka的命令行能做到的事十分少,这里就列举一些比较常用的命令行操作,方便测试。
topic的增删查
-
列出当前所有topic:
bin/kafka-topics.sh --zookeeper hadoop03:2181 --list
这里要指定服务器的地址。
-
创建topic:
bin/kafka-topics.sh --zookeeper hadoop03:2181 --create --replication-factor 3 --partitions 1 --topic first
这里要指定分区数,分片数,和topic名字。
-
删除topic
bin/kafka-topics.sh --zookeeper hadoop03:2181 --delete --topic first
这里指定一下topic名称就可以了。
发送和接收消息
首先是发送:
bin/kafka-console-producer.sh --broker-list hadoop03:9092 --topic first
这里要指定主机名和要发送到的topic
然后是接收:
bin/kafka-console-consumer.sh --zookeeper hadoop03:2181 --topic first
但这种方式提示已经过时了,我们采用新的:
bin/kafka-console-consumer.sh --bootstrap-server hadoop03:9092 --topic first
这样我们就可以在控制台来测试kafka了。
如果想看到之前没开启消费者时发送的内容,可以加一条参数:
bin/kafka-console-consumer.sh --bootstrap-server hadoop03:9092 --from-beginning --topic first
就可以看到了。
api操作
总的来说,我们使用kafka还是用api居多,这里就介绍一下最基本的API操作。
首先要导入依赖:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.11.0.0</version>
</dependency>
生产者——异步发送API
直接上代码:
package com.liuge.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
/**
* @ClassName: MyProducer
* @Description: kafka生产者类
* @author: LiuGe
* @date: 2021/1/17
*/
public class MyProducer {
public static void main(String[] args) {
// 1.创建kafka生产者的配置信息
Properties properties = new Properties();
// 2.指定连接的kafka集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop03:9092");
// 3.ack应答级别
properties.put(ProducerConfig.ACKS_CONFIG, "all");
// 4.重试次数
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
// 5.批次大小
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 6.等待时间
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// 7.RecordAccumulator 缓冲区大小
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
// 8.key,value的序列化类
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization" +
".StringSerializer");
// 9.创建生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 10.发送数据
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<>("first","liuge--" + i));
}
// 11.关闭资源
producer.close();
}
}
代码很简单,就不过多解释了。
生产者——异步发送API-带回调
package com.liuge.producer;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
/**
* @ClassName: CallBackProducer
* @Description: 带回调的生产者
* @author: LiuGe
* @date: 2021/1/17
*/
public class CallBackProducer {
public static void main(String[] args) {
// 1.创建配置信息
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop03:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
// 2.创建生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 3.发送数据
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<>("first", 0,"liuge","liuge--" + i), (metadata, exception) -> {
if (exception == null) {
System.out.println("partition==>" + metadata.partition());
System.out.println("offset==>" + metadata.offset());
}
});
}
// 4.关闭资源
producer.close();
}
}
生产者——自定义分区器
首先是自定义分区器
package com.liuge.patitioner;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
* @ClassName: MyPartitioner
* @Description: 自定义分区器
* @author: LiuGe
* @date: 2021/1/17
*/
public class MyPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
return 1;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}
然后我们要调用这个自定义分区器:
package com.liuge.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
/**
* @ClassName: PartitionProducer
* @Description: 自定义分区的生产者
* @author: LiuGe
* @date: 2021/1/17
*/
public class PartitionProducer {
public static void main(String[] args) {
// 1.创建配置信息
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop03:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
// 添加分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.liuge.patitioner.MyPartitioner");
// 2.创建生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 3.发送数据
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<>("first", "liuge--" + i), (metadata, exception) -> {
if (exception == null) {
System.out.println("partition==>" + metadata.partition());
System.out.println("offset==>" + metadata.offset());
}else{
exception.printStackTrace();
}
});
}
// 4.关闭资源
producer.close();
}
}
生产者——自定义过滤器
首先定义两个过滤器:
package com.liuge.interceptor;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Map;
/**
* @ClassName: CounterInterceptor
* @Description:
* @author: LiuGe
* @date: 2021/1/17
*/
public class CounterInterceptor implements ProducerInterceptor<String ,String> {
int success;
int error;
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
return record;
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
if(metadata!= null){
success++;
}else{
error++;
}
}
@Override
public void close() {
System.out.println("success = " + success);
System.out.println("error = " + error);
}
@Override
public void configure(Map<String, ?> configs) {
}
}
package com.liuge.interceptor;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Map;
/**
* @ClassName: TimeInterceptor
* @Description:
* @author: LiuGe
* @date: 2021/1/17
*/
public class TimeInterceptor implements ProducerInterceptor<String,String> {
@Override
public void configure(Map<String, ?> configs) {
}
@Override
public ProducerRecord<String,String> onSend(ProducerRecord<String,String> record) {
// 1.取出数据
String value = record.value();
// 2.创建一个新的ProducerRecord对象
return new ProducerRecord<>(record.topic(),record.partition(),record.key(),
System.currentTimeMillis() + "," + value);
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
}
@Override
public void close() {
}
}
然后在生产者中应用这两个过滤器:
package com.liuge.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @ClassName: InterceptorProducer
* @Description:
* @author: LiuGe
* @date: 2021/1/17
*/
public class InterceptorProducer {
public static void main(String[] args) {
// 1.创建kafka生产者的配置信息
Properties properties = new Properties();
// 2.指定连接的kafka集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop03:9092");
// 3.ack应答级别
properties.put(ProducerConfig.ACKS_CONFIG, "all");
// 4.重试次数
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
// 5.批次大小
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 6.等待时间
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// 7.RecordAccumulator 缓冲区大小
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
// 8.key,value的序列化类
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
List<String> interceptors = new ArrayList<>();
interceptors.add("com.liuge.interceptor.TimeInterceptor");
interceptors.add("com.liuge.interceptor.CounterInterceptor");
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);
// 9.创建生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 10.发送数据
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<>("first","liuge--" + i));
}
// 11.关闭资源
producer.close();
}
}
消费者——异步消费api
package com.liuge.consumer;
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 java.util.Arrays;
import java.util.Properties;
/**
* @ClassName: MyConsumer
* @Description: 测试消费者
* @author: LiuGe
* @date: 2021/1/17
*/
public class MyConsumer {
public static void main(String[] args) {
// 1.创建消费者配置信息
Properties properties = new Properties();
// 2.给配置信息赋值
// 连接的集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop03:9092");
// 开启自动提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
// 自动提交的延迟
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"1000");
// key,value的反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
// 消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"bigdata1");
// 重置消费者的offset
// properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
// 创建消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
// 订阅主题
consumer.subscribe(Arrays.asList("first","second"));
// 获取数据
while (true){
ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
// 解析并打印
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord.key() + "-->" + consumerRecord.value());
}
}
}
}
总结
总的来说,kafka的使用难度不是很大,但要了解其原理还是很复杂的。本文章只是简单记录了一下kafka的常用用法,没有涉及太复杂的内容。