文章目录
Apache Kafka
一、概述
Apache Kafka是一个分布式的流数据平台,代表三层含义:
-
Publish/Subscribe: 消息队列系统 MQ(Message Queue)
-
Process: 流数据的实时处理(Stream Process)
-
Store: 流数据会以一种安全、容错冗余存储机制存放到分布式集群中
架构
应用场景
- 构建实时的流数据管道,在系统和应用之间进行可靠的流数据传输
- 构建实时的流数据处理应用,对流数据进行转换和加工处理
核心概念
Cluster
: kafka支持一到多个服务构成的分布式集群,每一个服务实例成为Broker
Topic
: 某一个分类的消息的集合,如:订单的topic、商品的topic等Partition
: 一个Topic有若干个分区(Partition)构成,分区的数量在创建Topic时手动指定Replication
: 分区副本,是Partition的冗余备份分区,当Partition不可用时,ZooKeeper会自动将Replication(Follower)分区升级为Partition(Leader)分区Offset
: 分区中的Record的位置标示,每一个消费者都会记录自己的消费位置(offset)
Topic和Log
Each partition is an ordered, immutable sequence of records that is continually appended to—a structured commit log
Kafka的每一个分区(Partition),都是一个有序、不可变的持续追加的记录序列,Kafka会以一种结构化的提交日志保存分区中的数据。
注意:在分区中写入数据时,会在队列的末尾进行追加,每一个消费者都维护的有一个自己的消费位置(offset)
二、环境搭建
完全分布式的Kafka集群
准备工作
-
分布式集群中时钟同步
[root@HadoopNodeX ~]# yum -y install ntpdate [root@HadoopNodeX ~]# ntpdate -u ntp.api.bz 25 Sep 11:19:26 ntpdate[1749]: step time server 114.118.7.163 offset 201181.363384 sec [root@HadoopNodeX ~]# date Wed Sep 25 11:19:52 CST 2019
-
JDK1.8以上
-
开启ssh免密登陆
[root@HadoopNodeX ~]# ssh-keygen -t rsa # 先在所有集群夫妻武器都运行此命令,在运行下面的 [root@HadoopNodeX ~]# ssh-copy-id HadoopNode01 [root@HadoopNodeX ~]# ssh-copy-id HadoopNode02 [root@HadoopNodeX ~]# ssh-copy-id HadoopNode03
-
ZooKeeper集群服务运行正常
[root@HadoopNode0* zookeeper-3.4.6]# vi conf/zoo.cfg tickTime=2000 initLimit=10 syncLimit=5 dataDir=/home/zk/data clientPort=2181 server.1=hadoopnode01:2887:3887 server.2=hadoopnode02:2888:3888 server.3=hadoopnode03:2889:3889 [root@HadoopNode0* zookeeper-3.4.6]# cd /home/zk [root@HadoopNode0* zk]# mkdir data [root@HadoopNode0* zk]# vi data/myid # Node01 1 # Node02 2 # Node03 3 [root@HadoopNode0* zookeeper-3.4.6]# bin/zkServer.sh start conf/zoo.cfg # 如何判断zookeeper集群服务正常 [root@HadoopNode03 zookeeper-3.4.6]# bin/zkServer.sh status conf/zoo.cfg JMX enabled by default Using config: conf/zoo.cfg Mode: leader (一主两从)
安装配置
-
将安装包上传并复制到其它节点
# 上传到某一个服务器,并拷贝给其它服务器 [root@HadoopNode01 ~]# scp kafka_2.11-2.2.0.tgz root@hadoopnode02:~ kafka_2.11-2.2.0.tgz 100% 61MB 61.0MB/s 00:00 [root@HadoopNode01 ~]# scp kafka_2.11-2.2.0.tgz root@hadoopnode03:~ kafka_2.11-2.2.0.tgz
-
安装Kafka
[root@HadoopNode0* ~]# tar -zxf kafka_2.11-2.2.0.tgz -C /usr [root@HadoopNode01 kafka_2.11-2.2.0]# ll total 52 drwxr-xr-x. 3 root root 4096 Mar 10 2019 bin # 指令 drwxr-xr-x. 2 root root 4096 Mar 10 2019 config # 配置文件 drwxr-xr-x. 2 root root 4096 Oct 9 08:56 libs # 依赖jar包 -rw-r--r--. 1 root root 32216 Mar 10 2019 LICENSE -rw-r--r--. 1 root root 336 Mar 10 2019 NOTICE drwxr-xr-x. 2 root root 4096 Mar 10 2019 site-docs # 使用文档
环境配置
-
修改kafka核心配置文件
server.properties
[root@HadoopNode01 kafka_2.11-2.2.0]# vi config/server.properties broker.id=0 | 1 | 2 listeners=PLAINTEXT://HadoopNode0[1 | 2 | 3]:9092 log.dirs=/usr/kafka_2.11-2.2.0/data zookeeper.connect=HadoopNode01:2181,HadoopNode02:2181,HadoopNode03:2181
启动服务
-
启动
[root@HadoopNode0* kafka_2.11-2.2.0]# bin/kafka-server-start.sh -daemon config/server.properties [root@HadoopNode0* kafka_2.11-2.2.0]# jps 10386 Kafka 10517 Jps 3276 QuorumPeerMain
-
关闭
[root@HadoopNode0* kafka_2.11-2.2.0]# bin/kafka-server-stop.sh config/server.properties
三、基础使用
命令行操作
Topic使用
-
新建Topic
[root@HadoopNode01 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --topic t1 --partitions 3 --replication-factor 3 --create
-
展示Topic列表
[root@HadoopNode01 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --list
-
删除Topic
[root@HadoopNode02 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --delete --topic t2
-
描述Topic
[root@HadoopNode02 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --describe --topic t1 Topic:t1 PartitionCount:3 ReplicationFactor:3 Configs:segment.bytes=1073741824 Topic: t1 Partition: 0 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1 Topic: t1 Partition: 1 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0 Topic: t1 Partition: 2 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2 # 测试:kill掉Node03上的kafka服务实例 也就是broker【2】 [root@HadoopNode02 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --describe --topic t1 Topic:t1 PartitionCount:3 ReplicationFactor:3 Configs:segment.bytes=1073741824 Topic: t1 Partition: 0 Leader: 0 Replicas: 0,2,1 Isr: 0,1 Topic: t1 Partition: 1 Leader: 1 Replicas: 2,1,0 Isr: 1,0 Topic: t1 Partition: 2 Leader: 1 Replicas: 1,0,2 Isr: 1,0 # 测试:恢复运行Node03上的Kafka服务实例Broker[2],第三列信息不改变的原因:(分区的Leader都存在,不会触发ZK的故障转移),第五列信息不变 [root@HadoopNode02 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --describe --topic t1 Topic:t1 PartitionCount:3 ReplicationFactor:3 Configs:segment.bytes=1073741824 Topic: t1 Partition: 0 Leader: 0 Replicas: 0,2,1 Isr: 0,1,2 Topic: t1 Partition: 1 Leader: 1 Replicas: 2,1,0 Isr: 1,0,2 Topic: t1 Partition: 2 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
-
修改Topic
[root@HadoopNode02 kafka_2.11-2.2.0]# bin/kafka-topics.sh --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --alter --topic t1 --partitions 5
发布和订阅
-
发布消息
[root@HadoopNode01 kafka_2.11-2.2.0]# bin/kafka-console-producer.sh --broker-list HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092 --topic t1 >Hello World >Hello Kafka >Hello Hadoop
-
订阅消息
[root@HadoopNode02 kafka_2.11-2.2.0]# bin/kafka-console-consumer.sh --topic t1 --bootstrap-server HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092
JAVA Driver
Maven依赖
<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.2.0</version>
</dependency>
准备工作
-
配置Windows Hosts主机名和IP映射
192.168.11.20 HadoopNode00 192.168.11.21 HadoopNode01 192.168.11.22 HadoopNode02 192.168.11.23 HadoopNode03
生产者
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.UUID;
/**
* kafka 生产者的测试类
*/
public class ProducerDemo {
public static void main(String[] args) {
//1. 准备Kafka生产者配置信息
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092");
// string 序列化(Object ---> byte[])器
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);
//2. 创建kafka生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
//3. 生产记录并将其发布
ProducerRecord<String, String> record = new ProducerRecord<String, String>("t2", UUID.randomUUID().toString(),"Hello Kafka");
producer.send(record);
//4. 释放资源
producer.flush();
producer.close();
}
}
1) Kafka的消息生产者,负责生产数据(Record K\V\Timestamp),最终发布(Publish)保存到Kafka集群
2)数据的保存策略:
- 如果Record的Key不为
Null
,采用哈希算法:key.hashCode % numPartitions = 余数(分区序号)
- 如果Record的Key为
Null
, 采用轮询策略- 手动指定存放的分区
3) 数据会以一种分布式的方式保存在Kafka集群中,每一个分区都会维护一个队列的数据结构,新产生的数据会追加到队列的末尾,并且分配
offset
,4)数据在Kafka集群中默认最多保留7天(168Hours),不论是否消费,在保留周期到达后都会自动被删除。
5)数据在Kafka中可以进行重复消费,重置消费offset即可
消费者
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 java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
/**
* kafka消费者测试类
* 1. 订阅 subscribe
* 2. 拉取 pull
*/
public class ConsumerDemo {
public static void main(String[] args) {
//1. 指定kafka消费者的配置信息
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "HadoopNode01:9092,HadoopNode02:9092,HadoopNode03:9092");
// 反序列化器 byte[] ---> Object
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// 消费组必须得指定
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "group1");
//2. 创建kafka消费者对象
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties);
//3. 订阅主体topic
consumer.subscribe(Arrays.asList("t2"));
//4. 拉取新产生的记录
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(10));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.key() + "\t" + record.value() + "\t"
+ record.topic() + "\t" + record.offset()
+ "\t" + record.timestamp() + "\t" + record.partition());
}
}
}
}
1)消费者并不是独立存在,kafka中消费者会以消费组的方式进行组织和管理
2)消费组符合特征: 组外广播、组内负载均衡
- 组外广播: 保证不同的消费组,能够独立消费新产生的数据
- 组内负载均衡: 消息只会被消费组中的一个消费着进行处理,多个消费组提高了Kafka并行处理能力
3)消费者可以订阅一个到多个感兴趣的Topic,一旦这些Topic有新的数据产生,消费者会自动拉取新产生的数据,进行相应的业务处理
4)消费者在消费消息时,会维护一个消费的位置(offset),下一次消费时会自动从offset向后进行消费。
在kafka中数据会有一个默认的保留周期(7天),在保留期内数据是可以进行重复消费的,只需要重置消费者消费的offset即可。
5)
__consumer_offsets
是一个特殊topic,主要记录了Kafka消费组的消费位置。
四、高级部分
偏移量控制
Kafka消费者在订阅Topic时,会自动拉取Topic中新产生的数据。首次消费时使用默认的偏移量消费策略lastest
偏移量消费策略:
-
lastest(默认):如果有已提交的offset,从已提交的offset之后消费消息。如果无提交的offset,从最后的offset之后消费数据
-
earliest:如果有已提交的offset,从已提交的offset之后消费消息。如果无提交的offset,从最早的offset消费消息
// 注意:此配置项 修改偏移量消费策略的默认行为 properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
Kafka消费者消费位置offset,默认采用自动提交的方式,将消费位置提交保存到特殊Topic__consumer_offsets
中
自动提交策略:
// 默认自动提交消费的位置offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
// 默认每隔5秒提交一次消费位置
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,5000);
通常情况需要手动提交消费位置:
为什么需要手动提交消费位置(offset)的原因?
原因:如果自动提交消费位置,有可能在进行业务处理时出现错误,会造成数据没有被正确处理。
手动提交消费位置,可以保证数据一定能够被完整的正确处理。
// 关闭消费位置offset的自动提交功能
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
// 手动提交消费位置
consumer.commitSync();
消费方式
订阅(Subscribe)
消费者订阅1到N个感兴趣的Topic,一旦Topic中有新的数据产生,会自动拉取Topic分区内的所有数据
// 订阅(消费)Topic所有的分区
consumer.subscribe(Arrays.asList("t3"));
指定消费分区
消费者在消费数据时,可以只消费某个Topic特定分区内的数据
// 指定消费Topic的特定分区
consumer.assign(Arrays.asList(new TopicPartition("t3",0)));
重置消费位置
消费者在消费数据时,可以重置消费的offset,消费已消费的数据或者跳过不感兴趣的数据
consumer.assign(Arrays.asList(new TopicPartition("t3",0)));
// 重置消费位置
consumer.seek(new TopicPartition("t3",0),1);
消费组
(略)
自定义对象类型的传输
序列化接口
public interface Serializer<T> extends Closeable {
void configure(Map<String, ?> var1, boolean var2);
// 序列化方法
byte[] serialize(String var1, T var2);
default byte[] serialize(String topic, Headers headers, T data) {
return this.serialize(topic, data);
}
void close();
}
反序列化接口
public interface Deserializer<T> extends Closeable {
void configure(Map<String, ?> var1, boolean var2);
// 反序列化方法
T deserialize(String var1, byte[] var2);
default T deserialize(String topic, Headers headers, byte[] data) {
return this.deserialize(topic, data);
}
void close();
}
自定义对象
public class User implements Serializable {
private Integer id;
private String name;
private Date birthday;
// ...
}
导入工具包的依赖jar包
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
自定义编解码器类
package transfer;
import org.apache.commons.lang.SerializationUtils;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.Serializer;
import java.io.Serializable;
import java.util.Map;
/**
* 自定义对象的编解码器类
*/
public class ObjectCodec implements Serializer, Deserializer {
/**
* bytes[] ---> Object
* @param s
* @param bytes
* @return
*/
@Override
public Object deserialize(String s, byte[] bytes) {
return SerializationUtils.deserialize(bytes);
}
@Override
public void configure(Map map, boolean b) {
}
/**
* Object ---> bytes[]
* @param s
* @param o
* @return
*/
@Override
public byte[] serialize(String s, Object o) {
return SerializationUtils.serialize((Serializable) o);
}
@Override
public void close() {
}
}
测试
建议新创建Topic进行测试,避免旧的Topic中历史数据对我们产生干扰