文章目录
一、简介
Kafka 是一个分布式流媒体平台
kafka官网:http://kafka.apachecn.org/
(1)流媒体平台有三个关键功能:
- 发布和订阅记录流,类似于消息队列或企业消息传递系统。
- 以 容错的持久方式存储记录流 。
- 记录发生时处理流。
(2)Kafka通常用于两大类应用:
- 构建可在系统或应用程序之间可靠获取数据的实时流数据管道
- 构建转换或响应数据流的实时流应用程序
- topic:Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)
- producer:发布消息的对象称之为主题生产者(Kafka topic producer)
- consumer:订阅消息并处理发布的消息的对象称之为主题消费者(consumers)
- broker:已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
二、常见MQ对比
RabbitMQ:
优势:
1)支持语言非常广。
2)稳定性很好,采用Erlang语言开发。
3)吞吐量不算低,近十万级( 每秒5-10W条消息处理)。
4)拥有丰富的消息发送模式(简单,工作,路由,发布订阅,主题)。
缺点:
采用Erlang,太小众,研究源码很难,扩展产品功能比较难。
RocketMQ:
1)稳定好,高可用
2)吞吐量达到十万级
缺点:
1)应该在国内阿里系公司使用
2)语言支持偏少,只有Java和C++
Kafka:
优势:
1)高吞吐量,百万级
2)稳定性好,高可用,采用zookeeper进行注册
3)可以应用在大数据数据处理领域(Kafka Stream)
缺点:
1)语法支持只有Java,C++少量语言
2)依赖zookeeper进行注册
三、安装与配置
3.1 安装zookeeper
docker pull zookeeper
docker run -d --name zookeeper -p 2181:2181 -t zookeeper
3.2 安装kafka
docker pull wurstmeister/kafka
docker run -d --name kafka --publish 9092:9092 --link zookeeper --env KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 --env KAFKA_ADVERTISED_HOST_NAME=127.0.0.1 --env KAFKA_ADVERTISED_PORT=9092 --volume /Users/hebinyang/Desktop/zookeeper:/etc/localtime wurstmeister/kafka:latest
--volume左边是宿主机挂载目录,右边是docker目录
四、入门案例
4.1 依赖导入
<properties>
<kafka.client.version>2.0.1</kafka.client.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${kafka.client.version}</version>
</dependency>
</dependencies>
做一个java普通的生产者和消费者只需要依赖kafka-clients
即可。
4.2 消息生产者
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;
/**
* 消息生产者
* kafka的一条消息由key-value组成
* key:消息标记用于指定不同的分区内容
* value:消息主主体内容
* (默认走序列化,也可以选择字符串)
*/
public class ProducerQuickStart {
public static void main(String[] args) {
//1.配置参数
Properties properties = new Properties();
//启动服务器
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
//key序列化器
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer"); //修改序列化机制为字符串
//value序列化器
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
//2.创建生产者对象
KafkaProducer kafkaProducer = new KafkaProducer(properties);
//3.创建消息
//参数一: 主题名称 topic name (类似rabbitMQ的交换性exchange)
//参数二: key
//参数三: value
ProducerRecord<String,String> msg = new ProducerRecord<>("hello","001","hello kafka");
//4.发送消息
kafkaProducer.send(msg);
//5.释放连接
kafkaProducer.close();
}
}
4.3 消息消费者
package com.hntou.kafka.kafka_simple;
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.time.Duration;
import java.util.Collections;
import java.util.Properties;
/**
* 消费者
*/
public class ConsumerQuickStart {
public static void main(String[] args) {
//1.配置参数
Properties prop = new Properties();
//服务器地址
prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092");
//key反序列化
prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"rg.apache.kafka.common.serialization.StringDeserializer"); //修改为字符串的序列化机制
//value反序列化
prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"rg.apache.kafka.common.serialization.StringDeserializer");
//消费组
prop.put(ConsumerConfig.GROUP_ID_CONFIG,"helloGroup");
//2.创建消费者对象
KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer(prop);
//3.监听消息(监听主题topic_name)
kafkaConsumer.subscribe(Collections.singleton("hello")); //返回hello主题元素集合
//4.不断接收消息(所以不用关闭连接)
while(true){
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(1));
for(ConsumerRecord<String,String> record:records){
String key = record.key();
String value = record.value();
System.out.println(key+":"+value);
}
}
}
}
4.4 测试
先运行生产者,生产者会在创建主题的时候创建队列,如果先运行消费者直接去监听主题但是主题还没有就会抛出异常
第一次启动生产者,为了创建主题
启动消费者开始监听
五、kafka相关概念
红色箭头的消息传递给主副本(Leader),黑色箭头是主副本把消息同步到从副本(Follower)
在kafka概述里介绍了概念包括:topic、producer、consumer、broker,这些是最基本的一些概念,想要更深入理解kafka还要知道它的一些其他概念定义:
- 消息Message
Kafka 中的数据单元被称为消息message,也被称为记录,可以把它看作数据库表中某一行的记录。
- topic
Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)。
- 批次
为了提高效率, 消息会分批次写入 Kafka,批次就代指的是一组消息。
- 分区Partition (高并发)
一个主题可以分为一个或者n个分区,但是默认是一个分区。分区的好处就是为了提高消息的并发能力(吞吐量)
主题可以被分为若干个分区(partition),同一个主题中的分区可以不在一个机器上,有可能会部署在多个机器上,由此来实现 kafka 的伸缩性。topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个文件进行存储。一个partition中的数据是有序的,多个partition之间的数据是没有顺序的。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。
同一时间发送给不同的分区,所以提高发送消息的吞吐量。同一分区的不同消息是有序的先入先出,但是不同消息发送到不同的分区,顺序就不能保证
- Broker
一个独立的 Kafka 服务器就被称为 broker,broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。
- Broker 集群
broker 是集群 的组成部分,broker 集群由一个或多个 broker 组成,每个集群都有一个 broker同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。
- 副本Replica(分片)(高可用)
Kafka 中消息的备份又叫做 副本(Replica),副本的数量是可以配置的,Kafka 定义了两类副本:领导者副本(Leader Replica) 和 追随者副本(Follower Replica);所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从ISR列表(保持同步的副本列表)中删除,重新创建一个Follower。
一个分区可以存在多个副本,多个副本一定会选举一个主副本,主副本承担当前工作,所有生产者消息会发送到主副本,主副本会及时同步从副本,当主副本挂掉之后,会从其他从副本重新选举一个主副本,再把消息传递给新的主副本以至达到主从消息同步
- Zookeeper
kafka对与zookeeper是强依赖的,是以zookeeper作为基础的,即使不做集群,也需要zk的支持。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行重平衡。
- 消费者群组Consumer Group
生产者与消费者的关系就如同餐厅中的厨师和顾客之间的关系一样,一个厨师对应多个顾客,也就是一个生产者对应多个消费者,消费者群组(Consumer Group)指的就是由一个或多个消费者组成的群体。
- 偏移量Consumer Offset
偏移量(Consumer Offset)是一种元数据,它是一个不断递增的整数值,用来记录消费者发生重平衡时的位置,以便用来恢复数据。
- 重平衡Rebalance
消费者同组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。
5.1 生产者详解
5.1.1 发送消息的工作原理
从集群获取分区的leader(分区选择)
1.1 producer把消息发送给指定的topic
1.2 producer发送消息key给kafka
1.3 Kafka到指定的topic内,使用key进行hash运算,得到哈希值,用哈希值%分区总数=分区下标,如果没有提供key就是随机分区。
1.4 Kafka根据分区下标找到该分区下的主分区。
5.1.2 三种消息发送类型
1.异步发送并忘记(fire-and-forget)(用的最多)
把消息发送给服务器,并不关心它是否正常到达,大多数情况下,消息会正常到达,因为kafka是高可用的,而且生产者会自动尝试重发,使用这种方式有时候会丢失一些信息。
//发送消息
try {
producer.send(record);
}catch (Exception e){
e.printStackTrace();
}
应用场景:如果业务只关心消息的吞吐量,容许少量消息发送失败,也不关注消息的发送顺序,那么可以使用发送并忘记的方式 。
2.同步发送(用的很少)
使用send()方法发送,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功。
//发送消息
try {
RecordMetadata recordMetadata = producer.send(record).get();
System.out.println(recordMetadata.offset());//获取偏移量
}catch (Exception e){
e.printStackTrace();
}
如果服务器返回错误,get()方法会抛出异常,如果没有发生错误,我们就会得到一个RecordMetadata对象,可以用它来获取消息的偏移量。
应用场景:如果业务要求消息尽可能不丢失且必须是按顺序发送的,那么可以使用同步的方式 ( 只能在一个partation上 )。
3.异步发送+回调
调用send()方法,并指定一个回调函数,服务器在返回响应时调用函数。如下代码
//发送消息
try {
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e!=null){
e.printStackTrace();
}
System.out.println(recordMetadata.offset());
}
});
}catch (Exception e){
e.printStackTrace();
}
如果kafka返回一个错误,onCompletion()方法会抛出一个非空(non null)异常,可以根据实际情况处理,比如记录错误日志,或者把消息写入“错误消息”文件中,方便后期进行分析。
应用场景: 如果业务需要知道消息发送是否成功,并且对消息的顺序不关心,那么可以用异步+回调的方式来发送消息 。
达到Kafka最高并发量:
1)一个主题多个分区 + 异步发送+不回调。
达到Kafka数据一致性最高+消息有序(消息不丢失):
2)一个主题一个分区+ 同步发送 + 回调函数。
5.1.3 参数详解
生产者除了(bootstrap.servers、序列化器等)还有很多可配置的参数,在kafka官方文档中都有说明,大部分都有合理的默认值,所以没有必要去修改它们,不过有几个参数在内存使用,性能和可靠性方法对生产者有影响
acks : 指的是producer的消息发送确认机制。
acks = 0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应,也就是说,如果当中出现了问题,导致服务器没有收到消息,那么生产者就无从得知,消息也就丢失了。不过,因为生产者不需要等待服务器的响应,所以它可以以网络能够支持的最大速度发送消息,从而达到很高的吞吐量。
acks = 1(默认值):只要集群首领节点收到消息,生产者就会收到一个来自服务器的成功响应,如果消息无法到达首领节点,生产者会收到一个错误响应,为了避免数据丢失,生产者会重发消息。
acks = all:只有当所有参与赋值的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应,这种模式是最安全的,它可以保证不止一个服务器收到消息,就算有服务器发生崩溃,整个集群仍然可以运行。不过他的延迟比acks=1时更高。
acks :生产者从服务器收到的错误有可能是临时性错误,在这种情况下,retries参数的值决定了生产者可以重发消息的次数,如果达到这个次数,生产者会放弃重试返回错误,默认情况下,生产者会在每次重试之间等待100ms。
5.2 消费者详解
5.2.1 消费者工作原理
5.2.2 其他参数详解
消费重平衡Consumer Rebalance
消费者同组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。
enable.auto.commit
该属性指定了消费者是否自动提交偏移量,默认值是true。为了尽量避免出现重复数据和数据丢失,可以把它设置为false,由自己控制何时提交偏移量。如果把它设置为true,还可以通过配置auto.commit.interval.ms
属性来控制提交的频率。
auto.offset.reset
earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费。
latest(默认值):当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据。
none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常。
anything else:向consumer抛出异常。