思维导图:
引言
本文主要会在整体上对Kafka进行基础性介绍。所以,总体分为以下三个部分:
- 基本概念
- 配置安装
- 使用JAVA客户端
一.基本概念
多分区,多副本且基于Zookeeper协调的分布式消息系统。
1.1 Kafka工作体系
Kafka的工作体系主要由三大成员构成:
- 生产者 Producer:负责创建消息并投递到Kafka服务器中
- 消费者 Consumer:负责接收消息,通过连接到Kafka服务器获取消息并进行相应的逻辑处理
- 代理 Broker:可以独立的一个服务器,也可以是服务器集群,其作用就是通过Zookeeper为生产者和消费者提供服务并存储数据。
1.2 消息
1.2.1 主题Topic
主题Topic这一概念进行分类。生产者会将 消息发送到特定的主题,而消费者也会订阅主题并进行使用。
1.2.2 分区Partition
分区Partition。一个主题可以分为多个分区,每个分区所含有的消息是不同的。主题更多的时一个逻辑上的概念,而分区则可以在存储层面上看做可追加的日志。消息在被追加到特定的分区后都会分配一个偏移量offset作为分区中此消息的唯一标识。Kafka通过偏移量来保证消息在分区内的有序性。
每一个分区都可以位于不同的代理服务器Broker上,通过设置合理的分配规则,消息可以被均匀的分配到各大分区中,如下图:
1.2.3 副本Replica
多副本Replica以提升容灾能力。一个分区可以拥有多个副本,而副本之间则是一主多从的关系。其中,Leader副本负责处理读写请求,Follower副本负责同步Leader副本内的消息。由Zookeeper的机制可以猜测到主Leader副本和Follower副本之间并不会完全一样,当Leader副本出现问题后,会从Follower副本中重新选举出新的Leader副本。
在下图所示中,某主题有三个分区,每个分区的副本因子即副本个数为3,所以每个分区有1个Leader副本和2个Follower副本。
分区中的所有副本称为AR(Assigned Replicas),和Leader副本保持一致的组成ISR(In-Sync Replicas),和Leader之间的消息同步有一定滞后的组成OSR(Out-of Sync Replics),所以 AR = ISR + OSR。
在ISR中,高水位HW(High Watermark)标志着一个特定的偏移量,代表消费者最多只能拉取到HW之前的消息。因为HW及HW之后的消息可能还未同步。LEO(Log of End Offset)则代表着最新的将要写入的消息所占有的offset。所以ISR中最小的LEO即为整个分区的HW。
LEO同HW的变化则有如下4图进行演示:
二.安装与配置
在第一节中已经介绍道,Kafka是由Zookeeper提供协调服务的,所以使用Kafka之前需要安装Zookeeper并启动服务器。而Zooke是由Java驱动的,所以还需要安装JDK,这篇文章中就不复述这两者的安装和 使用了,感兴趣的读者可以去我的Zookeeper相关博客中看看。
2.1安装
下载的时候注意下载二进制的tgz包
//下载
wget http://mirror.bit.edu.cn/apache/kafka/2.2.0/kafka_2.11-2.2.0.tgz
//解压
tar -zxvf kafka_2.11-2.2.0.tgz
2.2 配置
2.2.1 创建集群文件夹
类似于Redis和Zookeeper集群的创建,在解压了Kafka的安装包后,我们在同级目录下创建只有一台机器的集群演示文件夹。
//kafka集群文件夹创建
mkdir kafka
//9092端口,并创建日志文件夹
mkdir kafka/kafka-9092
mkdir kafka/kafka-9092/log
//9093端口,并创建日志文件夹
mkdir kafka/kafka-9093
mkdir kafka/kafka-9093/log
//9094端口,并创建日志文件夹
mkdir kafka/kafka-9094
mkdir kafka/kafka-9094/log
2.2.2 配置文件修改
在解压后的Kafka安装包中找到config下的server.properties,复制三份并分别修改其配置
//创建三个配置文件并修改其配置,然后置入对应的文件夹中
//标志Kafka代理服务器Broker的唯一编号
broker.id=0
//客户端监听配置 PLAINTEXT表示协议,//后可以加上ip地址,表示私服内的访问,0.0.0.0表示都可访问,9092表示端口号
listeners=PLAINTEXT://:9092
//公服的客户端监听配置,配置和私服的一个意思
advertised.listeners=PLAINTEXT://192.168.42.128:9092
//日志地址,我们在创建集群文件夹的时候已经创建
log.dirs=/home/zhouhao/kafka/kafka-9092/log
//Zookeeper地址,我已经开启了Zookeeper的服务,没有启动的还需要启动一下
zookeeper.connect=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/kafka
broker.id=1
listeners=PLAINTEXT://:9093
advertised.listeners=PLAINTEXT://192.168.42.128:9093
log.dirs=/home/zhouhao/kafka/kafka-9092/log
zookeeper.connect=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/kafka
broker.id=2
listeners=PLAINTEXT://:9094
advertised.listeners=PLAINTEXT://192.168.42.128:9094
log.dirs=/home/zhouhao/kafka/kafka-9094/log
zookeeper.connect=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/kafka
2.2.3启动服务器
//启动三个服务器,在kafka软件的目录下
bin/kafka-server-start.sh ../kafka/kafka-9092/server-9092.properties
bin/kafka-server-start.sh ../kafka/kafka-9093/server-9093.properties
bin/kafka-server-start.sh ../kafka/kafka-9094/server-9094.properties
2.2.4 检查服务器可用状态
//创建主题
bin/kafka-topics.sh --zookeeper 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/kafka --create --topic topic-testTopic --replication-factor 3 --partitions 4
//查看主题信息
bin/kafka-topics.sh --zookeeper 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/kafka --describe --topic topic-testTopic
//消费者订阅主题,脚本使用时无反应,知道生产者脚本产生消息后,就会打印此消息
bin/kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094 --topic topic-testTopic
//生产者产生主题信息
bin/kafka-console-producer.sh --broker-list 127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094 --topic topic-testTopic
三.Java客户端的使用
Linux上的脚本命令毕竟只能作为演示和检验使用,这一节里会使用Java模仿上述例子。
- maven配置
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>
- log4j.properties配置
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
3.1 消费者客户端
/**
* Kafka 消费者客户端
*
* @author : zhouhao
* @date : Created in 2019/5/20 21:48
*/
public class ConsumerFastStart {
/**
* Kafka集群地址
*/
private static final String BROKE_LIST = "192.168.42.128:9092,192.168.42.128:9093,192.168.42.128:9094";
/**
* 主题名称
*/
private static final String TOPIC_NAME = "topic-testTopic";
/**
* 组ID
*/
private static final String GROUP_ID = "group.demo";
public static void main(String[] args){
Properties properties = new Properties();
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("bootstrap.servers", BROKE_LIST);
properties.put("group.id",GROUP_ID);
KafkaConsumer<String,String> consumer = new KafkaConsumer<>(properties);
consumer.subscribe(Collections.singletonList(TOPIC_NAME));
while (true){
ConsumerRecords<String,String> records = consumer.poll(Duration.ofMillis(1000));
for(ConsumerRecord<String,String> record : records){
System.out.println("-----" + record.value());
}
}
}
}
3.2 生产者客户端
/**
* Kafka 生产者客户端
*
* @author : zhouhao
* @date : Created in 2019/5/20 21:38
*/
public class ProducerFastStart {
/**
* Kafka集群地址
*/
private static final String BROKE_LIST = "192.168.42.128:9092,192.168.42.128:9093,192.168.42.128:9094";
/**
* 主题名称
*/
private static final String TOPIC_NAME = "topic-testTopic";
public static void main(String[] args){
Properties properties = new Properties();
properties.put("bootstrap.servers", BROKE_LIST);
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String,String> producer = new KafkaProducer<>(properties);
ProducerRecord<String,String> record = new ProducerRecord<>(TOPIC_NAME,"Hello,Kafka");
try{
producer.send(record);
} catch (Exception e){
e.printStackTrace();
}
producer.close();
}
}