参考文章:
https://baike.baidu.com/item/Kafka/17930165?fr=aladdin
https://blog.csdn.net/qq_29186199/article/details/80827085
http://www.linkedkeeper.com/detail/blog.action?bid=1016
一、基础知识
概述
Kafka是Apache基金会旗下的开源流处理软件,使用Scala和Java编写的高吞吐量的基于日志的分布式发布订阅消息系统,通常Kafka被用来当作消息广播以及消息队列来使用。
Kafka的程序包很小,很轻量,所以很适合用来封装集成进行二次开发。
相关术语
broker: Kafka集群包含一个或多个服务器(Kafka Server),这些服务器在Kafka中就被称为broker。
topic: 消息的主题,每条消息都属于一个主题,消费者订阅消息时即按照主题订阅。
partition: 一个topic对应多个partition,topic是逻辑层面的概念,partition是物理层面的概念,一个partition对应一个目录,目录中可以有很多文件(segment),主要是索引文件和日志文件,发布的消息就存储在这些日志文件中,再通过topic组织到一个消息主题中,供生产者和消费者读写。
segment file: 一个partition可以包含很多个segment file,每个segement file由两部分构成,分别是.index和.log,两者成对出现,index文件中存储索引数据,即每条消息在当前log中是第几条,并指向该条消息对应的物理偏移量,从而得以在log文件中快速定位读取消息,log文件中就是存储的消息数据。每当segment file达到一定的大小,就会产生新的segment file,这样设计的目的是为了避免单个日志文件太大,导致Kafka的读写性能急剧下降。
producer: 生产者,即产生消息的用户,负责发布消息到broker。
consumer: 消费者,即获取并使用的消息的用户,消费通过向服务器订阅topic来获取对应的消息。
consumer group: 消费分组,这是Kafka中一个比较独特的概念,每个消费者都属于一个特定的消费分组,订阅消息是以组为单位进行的,一个group订阅一个或多个topic,一个topic中的一条消息只会被一个group中的一个consumer接收到。所以基于这种特性,当我们需要使用的是消息队列,就将多个consumer关联到同一个group中,这样可以保证消息只会被消费一次。如果我们需要使用的是消息广播,那么一个group就只关联一个consumer,这样就可以保证所有的consumer都能够接收到消息。
offset:
读取消息的位置偏移量,这也是Kafka中一个很独特的概念。
Kafka与传统的消息队列不同,传统的消息队列消息被消费后,就会被删除,即由服务器来控制消息的分发,保证消息只被消费一次,而Kafka中消息的分发并不由服务器端控制,Kafka Server将接收到的消息写入日志文件,即partition中,consumer消费时就拿着offset来向Kafka Server请求,offset就指示了consumer要获取的消息位于partition中的位置信息,Kafka Server根据该位置信息取出数据并返回给consumer。
从这种模式可以看出,Kafka和传统的消息队列还有一个不同的地方就是,Kafka的消息其实可以重复消费,甚至通过设置offset可以从任意指定位置消费,而传统的消息队列是无法重复消费的。
offset由consumer管理,通过这种模式,Kafka将控制消息分发的压力转移给了客户端,而在传统的消息队列中,服务器为了控制消息分发,为了保证每个消息只被消费一次,需要采用同步机制,从而出现性能瓶颈,这也是为什么Kafka性能如此之高的一个原因。
replication: partition的副本,当使用Kafka集群时,一个partition会有多个副本,其中最先创建的是leader,其它均为follower,producer和consumer只和leader交互,follower只负责从leader中fetch同步数据,当leader宕机后会从follower中选举一个新的leader,从而尽量保证数据不丢失。
原理
工作流程
工作原理图如下:
- producer选择一个topic,发送消息到Kafka Server,Kafka Server通过一定的分配策略,将消息append到某个partition末尾,一次生产消息的过程即结束;
- Kafka Server接收到producer的消息后,将消息写入日志文件,并记录offset;
- consumer group中某一个consumer,选择一个topic,请求指定offset的消息数据;
持久化原理
Kafka是通过日志文件实现持久化的,也就是前面术语介绍中的segment file,topic可以包含多个partition,一个partition对应一个目录,目录下可以包含很多个segment file,segment file又包含.index和.log两个文件,当一个segment file达到一定大小时,会自动生成新的segment file,避免单个文件过大。
详情可参看术语介绍中的topic、partition、segment、offset。
二、Kafka常用命令
以下命令均基于windows,linux下也大同小异。
首先我们需要启动zookeeper,Kafka中默认集成了zookeeper,所以我们不需要额外下载安装,使用集成的即可,默认的zookeeper配置文件中默认端口是2181。
启动zookeeper的命令如下:
bin\windows\zookeeper-server-start.bat config\zookeeper.propertie
然后启动Kafka Server,默认配置文件中默认使用的端口是9092
启动Kafka Server命令如下:
bin\windows\kafka-server-start.bat config\server.properties
创建需要使用的topic,命令如下:
bin\windows\kafka-topics.bat --create --zookeeper <IP>:<port> --replication-factor 1 --partitions 1 --topic <topic>
–replication-factor为该topic需要备份的副本数量,应小于或等于集群中的broker数量,–partitions为该topic下的partition数量
修改topic的分区数量,命令如下:
bin\windows\kafka-topics.bat --zookeeper <IP>:<port> --partitions <num> --topic <topic> --alter
向broker-list中的broker下的某topic发送消息,控制台启动生产者命令如下:
bin\windows\kafka-console-producer.bat --broker-list <IP>:<port> --topic <topic>
从bootstrap-server罗列的broker中的某topic中接收消息,并声明当前consumer所属的group,控制台启动消费者命令如下:
bin\windows\kafka-console-consumer.bat --bootstrap-server <IP>:<port> --topic <topic> --group <group>
三、在Java中使用Kafka
通过Maven导入需要的依赖包,代码如下:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.2.0</version>
</dependency>
构建生产者,代码如下:
package com.dongrui.study.kafka;
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
public class KafkaProducerProxy<K, V> {
private KafkaProducer<K, V> producer;
public KafkaProducerProxy(Properties properties) {
producer = new KafkaProducer<K, V>(properties);
}
public KafkaProducerProxy() {
// 初始化生产者并设置属性
Properties properties = new Properties();
properties.put("bootstrap.servers", "172.24.108.223:9092");
properties.put("session.timeout.ms", "5000");
properties.put("acks", "all");
properties.put("retries", "0");
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
producer = new KafkaProducer<K, V>(properties);
}
public void sendMessage(String topic, V value) {
try {
// 发送消息
producer.send(new ProducerRecord<K, V>(topic, value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
构建消费者,代码如下:
package com.dongrui.study.kafka;
import java.time.Duration;
import java.util.HashSet;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
public class KafkaConsumerProxy<K,V> {
private KafkaConsumer<K, V> kafkaConsumer;
private HashSet<String> topics = new HashSet<>();
public KafkaConsumerProxy(Properties properties,String...topics) {
kafkaConsumer = new KafkaConsumer<K, V>(properties);
for (String topic : topics) {
this.topics.add(topic);
}
kafkaConsumer.subscribe(this.topics);
}
public KafkaConsumerProxy(String...topics) {
// 设置初始化属性并初始化
Properties properties = new Properties();
properties.put("bootstrap.servers", "172.24.108.223:9092");
properties.put("group.id", "group-1");
properties.put("enable.auto.commit", "true");
properties.put("auto.commit.interval.ms", "1000");
properties.put("auto.offset.reset", "earliest");
properties.put("session.timeout.ms", "30000");
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConsumer = new KafkaConsumer<K, V>(properties);
// 为该consumer订阅topic
for (String topic : topics) {
this.topics.add(topic);
}
kafkaConsumer.subscribe(this.topics);
}
public ConsumerRecords<K, V> received() {
// 没有消息时,poll会阻塞设置的时长,直到收到消息
return kafkaConsumer.poll(Duration.ofMinutes(1));
}
}
完整demo源码位于 https://gitee.com/imdongrui/study-repo.git 仓库中kafka目录下,需要科自行获取
踩过的坑
windows powershell的坑
在windows powershell中执行创建生产者的命令,有不能加载出输入行的问题,开始一直以为是kafka server部署有问题,但经过折腾最终发现是window powershell的问题。
单个生产者发送消息不要太频繁
for (int i = 0; i < 10; i++) {
value = format.format(new Date()) + " good boy kafka " + i;
kafkaProducerProxy.sendMessage("study", value);
}
如上所示代码,由于for循环执行太快,导致消息不能成功发送,偶尔又能够成功发送,也是几经折腾,最终发现加上sleep后能够正常发送,暂不清楚是kafka-clien的问题还是kafka server的问题,修改后的代码如下:
for (int i = 0; i < 10; i++) {
value = format.format(new Date()) + " good boy kafka " + i;
kafkaProducerProxy.sendMessage("study", value);
Thread.sleep(1);
}