一、kafka概述
1.1 概念
kafka是一个分布式的基于发布/订阅模式的消息队列(message queue),主要应用于大数据实时处理领域。
kafka作为集群运行在一个或多个服务器上,可以跨越多个数据中心。
kafka存储的流记录在类别中心称为topic。
每个记录由一个键、一个值和一个时间戳组成。
1.2 kafka通常用于两大类应用
构建可靠的在系统或者应用程序之间获取数据的实时流数据管道;
构建转换数据流或对数据流作出反应的实时流应用程序。
-
流平台有三个关键功能
发布和订阅记录流,类似于消息队列或企业消息传递系统;
以容错、持久的方式存储记录流;
当容错发生时,处理记录流。
1.3 kafka有五个核心API
-
生产者API:允许应用程序将记录流发布到一个或多个kafka主题;
-
消费者API:允许应用程序订阅一个或多个主题并处理向其生成得记录流;
-
流API:允许应用程序充当流处理器,从一个或多个主题消耗输入流,并产生输出流到一个或多个输出主题,从而有效的将输入流转换为输出流;
-
连接器API:允许构建和运行可重用的生产者或使用者,将kafka主题连接到现有的应用程序或数据系统;
-
adminAPI:管理员的API。
1.4 Kafka的特点
- 类似与消息队列和商业的消息系统,kafka提供对流式数据的发布和订阅
- kafka提供一种持久的容错的方式存储流式数据
- kafka拥有良好的性能,可以及时的处理流式数据
基于以上三种特点,kafka在以下两种应用之间流行:
- 需要在多个应用和系统间提供高可靠的实时数据通道
- 一些需要实时传输数据及及时计算的应用
此外,kafka还有以下特点:
- kafka主要集群方式运行在一个或多个可跨多个数据中心的服务器上
- kafka集群将数据按照类别记录存储,这种类别在kafka中称为主题
- 每条记录由一个键、一个值和一个时间戳组成
1.5 kafka基础架构
- Producer:消息生产者,就是向kafka broker发消息的客户端;生产者负责将记录分配到topic的指定partition中;
- Consumer:消息消费者,向kafka broker取消息的客户端;每个消费者都要维护自己读取数据的offset;低版本0.9之前将offset默认保存在zookeeper中,0.9及之后默认保存在kafka的“__consumer_offsets”主题中;
- Consumer Group(CG):消费者组,由多个consumer组成;一个topic可以有多个consumer group;消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费,消费者组之间互不影响;所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者;
- Broker:一台kafka服务器就是一个broker;一个集群由多个broker组成;一个broker可以容纳多个topic;
- Topic:就是数据主题,可以理解为一个队列,生产者和消费者面向的都是一个topic;一个topic可以有零个、一个或多个订阅写入它的数据的使用者;
- Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上;一个topic可以分为多个partition,每个partition是一个有序的队列;每个分区都有偏移量(offset);
- Offset:数据会按照时间顺序被不断地追加到分区的一个结构化的commit log中,每个分区中存储的记录都是有序的,且顺序不可变,这个顺序是通过一个称之为offset的id来唯一标识,因此也可以认为offset是有序且不可变的;偏移量由消费者所控制;
- Replica:副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不会丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower;
- leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader;
- follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步;leader发生故障时,某个follower会成为新的follower。
- zookeeper:依赖集群保存meta信息;
- kafka里面的记录默认保留两天,超过两天,就会删除(可以配置)。
kafka的性能和数据大小无关,所以长时间存储数据没有什么问题
- 为方便扩展,并提高吞吐量,一个topic分为多个partition
- 配合分区的设计,提出消费者组的概念,组内每个消费者并行消费
- 为提高可用性,为每个partition增加若干副本,类似NameNode HA
1.6 kakfa作为一个信息传递系统
-
kafka关于流的概念与传统的企业消息传递系统相比如何?
- kafka的消费群体概念概括了消息队列的两种模式;与队列一样,使用者组允许将处理划分为进程的集合(使用者组的成员);与发布者一样,kafka允许向多个消费群体广播消息;
- kafka模型的优点在于,每个主题都有这两种特性,它既可以缩放处理,也可以是多用户,没有必要选择其中一种或另一种;
- kafka比传统的短信系统有更强的订购保证;
- 传统队列按顺序在服务器上保留记录,如果多个使用者从队列中消费记录,则服务器按照存储的顺序分发记录;但是,虽然服务器按顺序分发记录,但记录是异步传递给使用者的,因此它们可能会在不同的使用者上出现故障;这实际上意味着记录的排序在并行消费的存在下丢失;消息传递系统通常通过一个只允许一个进程从队列中使用的“独占使用者”的概念来解决这个问题,但这意味着在处理中没有并行性;
- kafka是通过在主题中具有并行性分区的概念,kafka能够为用户进程池提供排序保证和负载平衡;这是通过主题中的分区分配给使用者组中的使用者来实现的,这样每个分区就会被组中的一个消费者使用;通过这样做,就可以确保使用者是该分区的唯一读者,并按顺序使用数据;由于有许多分区,这仍然平衡了许多使用者实例的负载;注意,在使用者组中不能有比分区更多的使用者实例。
-
消息队列的两种模式
-
点对点模式(一对一,消费者主动拉取数据,消息收到后消息清楚)
消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息;消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息;queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
-
发布/订阅模式(一对多,消费者消费数据之后不会清楚消息)
消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息;和点对点方式不同,发布到topic的消息会被所有订阅者消费。
-
1.7 kafka作为一个存储系统
- 任何允许发布与消息分离的消息队列都有效的充当了在役消息的存储系统;kafka的不同之处在于它是一个非常好的存储系统;
- 写入kafka的数据被写入磁盘并复制以实现容错;kafka允许生产者等待确认,这样写就不会被认为是完整的,知道它被完全复制,并保证即使服务器被写入失败,它也会持久化;
- kafka使用刻度良好的磁盘结构,无论服务器上有50KB还是50TB的持久数据,kafka都会执行相同的操作;
- 由于认真对待存储并允许客户端控制其读取位置,可以将kafka看作是一种专用于高性能、低延迟提交日志存储、复制和传播的专用分布式文件系统。
1.8 用于流处理的kafka
- 仅仅读、写和存储数据流是不够的,其目的是支持流的实时处理;
- 在kafka中,流处理器是指从输入主题获取连续数据流、对输入执行某些处理并生成连续数据流以输出主题的任何东西;
- 可以直接使用生产者和使用者API进行简单处理;然而,对于更复杂的转换,kafka提供了一个完全集成的流API;这允许构建应用程序,这些应用程序可以进行重要的处理,从流中计算聚合或将流连接在一起;
- 这个工具有助于解决这类应用程序面临的困难问题:处理无序数据,以代码更改的形式重新处理输入,执行有状态的计算等;
- Streams API建立在kafka提供的核心原语之上:它使用生产者和使用者API作为输入,使用kafka作为有状态存储,并且用相同的组机制在流处理器实例之间进行容错。
1.9 把碎片放在一起
- 这种消息传递、存储和流处理的组合似乎不寻常,但对于kafka作为流媒体平台的角色来说,这是至关重要的;
- 像HDFS这样的分布式文件系统允许存储用于批处理的静态文件;实际上,这样的系统允许存储和处理;历史过去的数据;
- 传统的企业消息传递系统允许处理订阅后到达的未来消息;以这种方式构建的应用程序将在未来数据到达时处理它;
- kafka将这两种功能结合在一起,这两者的结合对于kafka作为流媒体应用程序的平台以及流数据管道的使用都是至关重要的;
- 通过将存储和低延迟订阅相结合,流应用程序可以以同样的方式处理过去和未来的数据;也就是说,一个应用程序可以处理历史数据和存储的数据,但是当它到达最后一个记录时,它可以在将来的数据到达时继续处理,而不是结束;这是流处理的一个广义概念,它包括批处理以及消息驱动的应用程序;
- 同样,对于流数据管道,对实时事件的订阅组合,使得可以在非常低延迟的管道中使用kafka;但是,能够可靠地存储数据的能力使其能够用于必须保证数据传递的关键数据,或者用于与只定期加载数据或可能长时间下降以进行维护的脱机系统的集成;流处理设备可以在数据到达时进行数据转换。
二、集群部署
2.1 单机
-
解压,重命名,删除tar包
-
启动zookeeper
nohup bin/zookeeper-server-start.sh config/zookeeper.properties &
-
启动服务器
nohup bin/kafka-server-start.sh config/server.properties &
-
创建一个主题
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication 1 --partitions 1 --topic kafka-hw
-
查看主题列表
bin/kafka-topics.sh --list --bootstrap-server localhost:9092
-
描述主题
bin/kafka-topics.sh --describe --bootstrap-server localhost:9092 --topic kafka-hw
-
创建生产者
bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic kafka-hw
-
创建一个消费者(默认只有启动之后的数据才可以读到)
--from-beginning(从头开始读)
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic kafka-hw --from-beginning
2.2 集群
-
修改配置文件(conf/server.properties)
# 每一台的kafka的broderid都不能一样 broker.id=1 # 日志的目录千万不要写到tmp下面 log.dirs=/data/kafka/data/kafka-logs # zookeepr,要配置上自己的zookeeper集群 zookeeper.connect=node7-1:2181,node7-2:2181,node7-3:2181
-
同时启动kafka
nohup bin/kafka-server-start.sh config/server.properties &
-
创建一个主题
bin/kafka-topics.sh --create --zookeeper node7-1:2181,node7-2:2181,node7-3:2181 --replication-factor 1 --partitions 1 --topic test
其中
--topic
:定义topic名;--replication-factor
:定义副本数,这里设置的是当前实际的副本数,副本数不能超过broker的数量,hdfs上设置的副本数是最大副本数;--partitions
:定义分区数bin/kafka-topics.sh --create --bootstrap-server node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092 --replication-factor 1 --partitions 1 --topic test
问题:消费者消费数据--zookeeper和--bootstrap-server的区别是什么?
对于消费者,kafka中有两个设置的地方: 对于老的消费者,由--zookeeper参数设置; 对于新的消费者,由--bootstrap-server参数设置; 如果使用了--zookeeper参数,那么consumer的offset信息将会存放在zk之中,查看的方法是使用./zookeeper-client,然后 ls /consumers/[group_id]/offsets/[topic]/[broker_id-part_id],这个是查看某个group_id的某个topic的offset 如果使用了--bootstrap-server参数,那么consumer的信息将会存放在kafka之中
-
查看主题
bin/kafka-topics.sh --list --zookeeper node7-1:2181,node7-2:2181,node7-3:2181
bin/kafka-topics.sh --list --bootstrap-server node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092
-
发送一些消息(生产者)
bin/kafka-console-producer.sh --broker-list node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092 --topic test
-
启动一个消费者
bin/kafka-console-consumer.sh --bootstrap-server node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092 --topic test --from-beginning
-
描述主题
bin/kafka-topics.sh --describe --zookeeper node7-1:2181,node7-2:2181,node7-3:2181 --topic test
-
删除一个主题
bin/kafka-topics.sh --delete --zookeeper node7-1:2181,node7-2:2181,node7-3:2181 --topic test
需要server.properties中设置
delete.topic.enable=true
否则只是标记删除
2.3 其他
-
创建主题(多分区多副本)
bin/kafka-topics.sh --create --zookeeper node7-1:2181,node7-2:2181,node7-3:2181 --replication-factor 3 --partitions 2 --topic multi-test
-
描述主题
bin/kafka-topics.sh --describe --zookeeper node7-1:2181,node7-2:2181,node7-3:2181 --topic multi-test
2.4 高可用
创建的主题一定要是多个分区,多个副本数。
杀死领导者,leader会变成另外一个,不影响之后的使用。
一个生产者,多个消费者,生产者生产一次,消费者每次都消费。
三、kafka的导入导出操作
-
输出一个字符串到文件中(>:覆盖,>>:追加)
echo -e 'aaaaaa\nbbbbbbb' > ~/kafka.txt
-
配置文件
config/connect-file-source.properties
config/connect-file-sink.properties
config/connect-standalone.properties
-
启动命令
bin/connect-standalone.sh config/connect-standalone.properties config/connect-file-source.properties config/connect-file-sink.properties
-
目的:数据都是来自生产者(sh文件),变成了一个文本文件,kafka中的数据导出为一个文件。
四、Java连接kafka
如何看或者码别人的代码:
- 从main函数一步一步往下走;
- 如果是调用函数,就往里跟,执行完就出来;
- 看源码不要指望一遍就能看懂,需要多看几遍;
- 看源码的步骤要和程序执行的步骤一样。
ProducerThread.java
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.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.IntegerSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* 使用多线程
* 启动生产者都带了哪些参数
*/
public class ProducerThread implements Runnable {
// 定义一个topic
private String topic = "test-clus";
// 发送消息总条数
private int totalCount = 10;
// kafka的生产者
private KafkaProducer<Integer, String> producer;
/**
* 外部不能new
*/
public ProducerThread() {
// 启动生产者的配置信息
Map<String, Object> configsMap = new HashMap<String, Object>();
// bootstrap-server
// configsMap.put("bootstrap.server", "node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092");
configsMap.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092");
// 名称不能重复
configsMap.put(ProducerConfig.CLIENT_ID_CONFIG, "myProducer");
// 序列化
configsMap.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class.getName());
configsMap.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 初始化kafka生产者
this.producer = new KafkaProducer<Integer, String>(configsMap);
}
/**
* 线程的主体方法
*/
@Override
public void run() {
System.out.println("---线程启动---" + Thread.currentThread());
int key = 0;
String recordStr = "生产者发送消息";
try {
// 循环
for (int i = 0; i < totalCount; i++) {
String value = recordStr + key;
ProducerRecord<Integer, String> record = new ProducerRecord<Integer, String>(topic, key, value);
/*
向kafka中发送消息
参数1:ProducerRecord(消息队列中的消息)
*/
Future<RecordMetadata> future = this.producer.send(record);
System.out.println("---发送消息体:key:" + key + ",value:" + value + ",返回值:" + future.get());
key ++;
Thread.sleep(1 * 1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
ConsumerThread.java
import kafka.utils.ShutdownableThread;
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.IntegerDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
/**
* 消费者
*/
public class ConsumerThread extends ShutdownableThread {
private String topic = "test-clus";
// kafka的消费者
private KafkaConsumer<Integer, String> consumer;
public ConsumerThread() {
super("myConsumer", false);
// 启动生产者的配置信息
Map<String, Object> configsMap = new HashMap<String, Object>();
// url
configsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "node7-1:9092,node7-2:9092,node7-3:9092,node7-4:9092");
// 反序列化
configsMap.put("key.deserializer", IntegerDeserializer.class.getName());
configsMap.put("value.deserializer", StringDeserializer.class.getName());
// 间隔时间
configsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
/*
取出消息的类型
最早:自动将偏移量重置为最早的偏移量,latest
最新:自动将偏移量重置为最新偏移量
None:如果未为消费者组找到以前的偏移量,则向消费者抛出异常
其他任何事请:向消费者抛出异常,anything else
*/
configsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
// 设置groupId
configsMap.put(ConsumerConfig.GROUP_ID_CONFIG, "groupId");
// 设置自动提交
configsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
// 会话超时时间
configsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
// 初始化consumer
this.consumer = new KafkaConsumer<Integer, String>(configsMap);
}
/**
* 相当于run方法
*/
@Override
public void doWork() {
// System.out.println(Thread.currentThread() + "---线程循环---");
// List list = new ArrayList();
// list .add(topic);
List<String> topicList = Collections.singletonList(topic);
/*
订阅
参数是一个集合
*/
this.consumer.subscribe(topicList);
// 查看消息
ConsumerRecords<Integer, String> records = this.consumer.poll(Duration.ofSeconds(2));
// 循环取消息
for (Iterator iterator = records.iterator(); iterator.hasNext(); ) {
ConsumerRecord record = (ConsumerRecord) iterator.next();
String topic = record.topic();
String key = record.key() + "";
String value = record.value() + "";
long offset = record.offset();
System.out.println("--consumer--topic:" + topic + ",key:" + key + ",value:" +value + ",offset:" + offset);
}
}
}
ClientMain.java
public class ClientMain {
public static void main(String[] args) {
System.out.println("---启动kafka---");
// 创建一个生产者
ProducerThread producer = new ProducerThread();
// 启动
new Thread(producer).start();
// 启动消费者
ConsumerThread consumer = new ConsumerThread();
new Thread(consumer).start();
System.out.println("---启动完成---");
}
}