kafka 基础知识
1.kafka 概述
kafka 是一个分布式的中间件,支持多分区、多副本、多订阅者,基于zookeeper协调的分布式消息系统。
1.1 消息队列的两种模式
1)点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
消息生产者生产消息发送到Queue中, 然后消息消费者从Queue中取出并且消费消息。
2)发布/订阅模式(一对多,消费者消费数据之后不会清除消息)
消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。
点对点方式不同,发布到 topic 的消息会被所有订阅者消费。
3)消息队列的好处:
异步、削峰、解耦
1.2 kafka 特点
1.高吞吐量、低延迟
2.扩展性:kafka集群支持热扩展
3.持久性:支持数据持久化到磁盘
4.容错性:允许集群节点失败(n个副本,n-1节点存在)
5.高并发:多个客户端写入
1.3 kafka 架构
核心概念:
0.9 版本之前是依赖于zookeeper存储offset,0.9之后offset存储在特定的topic中
1)zookeeper:
(1) Kafka 通过 zookeeper 来存储集群的 meta 信息。
(2) 一旦controller所在broker宕机了,此时临时节点消失,集群里其他broker会一直监听这个临时节点,发现临时节点消失了,就争抢再次创建临时节点,保证有一台新的broker会成为controller角色。–选举controller
(3)存储consumer的offset信息。
2)producer:
消息生产者,发布消息到 Kafka集群的终端或服务。
3)broker:
Kafka集群中包含的服务器。
4)topic
每条发布到 Kafka集群的消息属于的类别,即 Kafka是面向 topic 的。可以理解为一个队列, 生产者和消费者面向的都是一个 topic;
5)partition
partition 是物理上的概念,每个 topic 包含一个或多个 partition。为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列。
6)副本replica:
partition 的副本,保障 partition 的高可用。kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,一个 leader 和若干个follower。
7)leader:
replica 中的一个角色, producer 和 consumer 只跟 leader 交互。
leader partition 作用:
1.消费者读取数据,生成者写数据。都是在leader partition
2.维护了一个ISR,成为副本列表(a,b,c)先写到a副本里面,然后再写到b,c
三个副本列表消息写完说明这个消息真正的写完了。如果c有问题,说明整个写入是失败了。如果c写入不成功会被我们leader partition给删除掉。(a,b)。
8)follower
replica 中的一个角色,从 leader 中复制数据。定期的从leader partition上同步数据。保持和 leader 数据的同步。 leader 发生故障时,某个 follower 会成为新的 leader。(心跳机制)
9)偏移量offset
每条数据都有一个offset,是数据在该partition的中的唯一标识。每个consumer会保存其消费到的offset的位置。消费偏移量专门的topic的__consumer_offset中。0.10x保存在zk中
1.4 数据存储
consumer_offsets也是topic,各个消费者的偏移量记录
test-0/test-1 test是topic名称,test-0/1/2/3/4是test的这个topic的那个分区目录。
由于生产者生产的消息不断追加log末尾,防止过大影响性能,采用分片和索引机制。将每个partition分为多个segment,每个segment对应两个文件。一个是log和.index文件。该文件夹命名是topic名称-分区序号
其中.index和log名字是当前segment的第一条消息的offset命名。index索引指向的是message的物理偏移信息。
2.命令行操作
2.1 创建topic
bin/kafka-topics.sh --zookeeper dev0:2181,dev1:2181,dev2:2181 --create --replication-factor 3 --partitions 3 --topic test2
–replication-factor 副本数量
–partitions 分区数量
–topic topic名称
2.2 查看当前系统的中所有topic
bin/kafka-topics.sh --zookeeper dev0:2181,dev1:2181,dev2:2181 --list
2.3 删除topic
bin/kafka-topics.sh --delete --topic test2 --zookeeper dev0:2181,dev1:2181,dev2:2181
delete.topic.enable=true
2.4 查看某个topic的详情
bin/kafka-topics.sh --zookeeper dev0:2181,dev1:2181,dev2:2181 --describe --topic test
ISR:in sync relicas 同步副本列表
leader:95 leader partition是机器数,replicas:副本所在机器broker
2.5 修改分区数
bin/kafka-topics.sh --zookeeper dev0:2181,dev1:2181,dev2:2181 --alter --topic test --partitions 5
kafka 只支持增加分区,不支持减少分区
原因:减少分区,代价太大
2.6 topic动态更新
bin/kafka-topics.sh --zookeeper dev0:2181,dev1:2181,dev2:2181 --entity-type topics
2.7 生产者producer
bin/kafka-console-producer.sh --broker-list dev0:9092 --topic test
2.8 消费者
bin/kafka-console-consumer.sh --bootstrap-server dev0:9092 --topic test
想消费之前的数据–从头消费
bin/kafka-console-consumer.sh --bootstrap-server dev0:9092 --topic test --from-beginning
指定要消费的分区,和要消费的起始offset
bin/kafka-console-consumer.sh --bootstrap-server dev0:9092 --topic test --offset 2 --partition 0
3.生产者api
public class ProducerDemo {
public static void main(String[] args) {
Properties props = new Properties();
//设置kafka集群的地址
props.put("bootstrap.servers",
"dev0:9092,dev1:9092,dev2:9092");
//设置ack模式,all是最慢的但是最安全
props.put("acks","all");
//失败重试次数
props.put("retries",0);
//数据发送批次大小
props.put("batch.size",10);
//数据序列化和反序列化
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//设置幂等性--是否开启
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,"false");
/**
* 构造生产者实例对象
*/
// 创建一个Producer实例:线程资源,跟各个broker建立socket连接资源
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
/**
* 构造消息发送
* 如果发送消息,消息不指定key,那么我们发送的这些消息,会被轮训的发送到不同的分区。
* 如果指定了key。发送消息的时候,客户端会根据这个key计算出来一个hash值,
* 根据这个hash值会把消息发送到对应的分区里面。
*/
for(int i=0;i<2;i++) {
ProducerRecord<String, String> msg = new ProducerRecord<>("test", "name"+i, "this is test");
producer.send(msg);
}
producer.close();
}
}
3.1 kafka 发送消息两种方式
1.同步发送
//第二种方式:这是同步发送的模式
producer.send(record).get();
// 你要一直等待人家后续一系列的步骤都做完,发送消息之后
// 有了消息的回应返回给你,你这个方法才会退出来
//第二种方式:这是同步发送的模式
producer.send(record).get();
// 你要一直等待人家后续一系列的步骤都做完,发送消息之后
// 有了消息的回应返回给你,你这个方法才会退出来。
2.异步发送
--异步发送
//kafka发送数据有两种方式:
//1:异步的方式。
//异步发送不管是否响应,一直发,集体接收
for(int i=0;i<2;i++) {
ProducerRecord<String, String> msg = new ProducerRecord<>("test", "name"+i, "this is test");
producer.send(msg,new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
// 消息发送成功
System.out.println("消息发送成功");
} else {
// 消息发送失败,需要重新发送
}
}
});
}
3.2整体代码实现
public class ProducerDemo {
public static void main(String[] args) throws Exception {
Properties props = new Properties();
//设置kafka集群的地址
props.put("bootstrap.servers",
"dev0:9092,dev1:9092,dev2:9092");
//设置ack模式,all是最慢的但是最安全
props.put("acks","all");
//失败重试次数
props.put("retries",0);
//数据发送批次大小
props.put("batch.size",10);
//数据序列化和反序列化
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//设置幂等性--是否开启
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,"false");
//
/**
* 构造生产者实例对象
*/
// 创建一个Producer实例:线程资源,跟各个broker建立socket连接资源
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
/**
* 构造消息发送
* 如果发送消息,消息不指定key,那么我们发送的这些消息,会被轮训的发送到不同的分区。
* 如果指定了key。发送消息的时候,客户端会根据这个key计算出来一个hash值,
* 根据这个hash值会把消息发送到对应的分区里面。
*/
for(int i=0;i<2;i++) {
ProducerRecord<String, String> msg = new ProducerRecord<>("test", "name"+i, "this is test");
producer.send(msg,new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
// 消息发送成功
System.out.println("消息发送成功");
} else {
// 消息发送失败,需要重新发送
}
}
});
}
ProducerRecord<String, String> record = new ProducerRecord<>("test", "name", " testlong");
//第二种方式:这是同步发送的模式
producer.send(record).get();
// 你要一直等待人家后续一系列的步骤都做完,发送消息之后
// 有了消息的回应返回给你,你这个方法才会退出来
producer.close();
}
}
3.3 producer发送数据原理
Producer 发送消息原理:
broker启动起来的时候都会把元数据在zk上注册。会去竞争谁是主节点。主节点是controller。谁先注册成功谁就拿到controller这个角色。controller 会监听zk的目录。监听元数据变化。并可以从zk同步到controller。会把同步的元数据分发给其他的broker。
生产者,生产者会把数据封装成一个对象,ProducerRecord对象,里面包含了要发送的topic,然后这个对象交给序列化器。将对象转换成二进制进行传输。
具体在哪个分区,分区key,消息内容,timestamp时间戳,分区器要往一个主题或者topic发送消息。发送到那个分区里面。leader partition。(获取元数据)leadpartition 是要承接读写的任务。followerpartition定期的leader partitions去拉取数据。
缓冲区:send 线程,从缓冲区里获取数据。多条数据封装成一个batch.通过这个线程把数据发送到leader partitions 的broker,写到os cache 缓存里面。达到时间或者空间条件,刷写到磁盘里面。
3.4 几个常见的生产者的问题:
1.生产者如何提高吞吐量
1.1.buffer.memory:设置发送消息的缓冲区,默认值是33554432,就是32MB。写数据变慢。如果发送消息出去的速度小于写入消息进去的速度,就会导致缓冲区写满。(调大)
1.2 compression.type,默认是none,不压缩,但是也可以使用lz4压缩,效率还是不错的。减小数据量,提升吞吐量,默认值是:16384,就是16kb。
1.3 batch.size,设置每个batch的大小,如果batch太小,会导致频繁网络请求,吞吐量下降。
1.4 linger.ms,这个值默认是0,意思就是消息必须立即被发送。一般设置一个100毫秒之类的,满了100ms 或者是满了16kb的时候就会发送出去。
1.3和1.4结合使用
3.5 分区器-分区
1.没有设置key的时候:
我们的消息就会被轮询的发送到不同的分区。
2.设置了key
kafka 自带的分区器,会根据key计算出一个hash值。这个哈希值对应某一个分区。如果key相同。那么hash值必然相同,key相同的值。必然会分到同一个分区
3.自定义分区。
props.put(“potitioner.class”,“com.zhss.HotDataPartitioner”);
3.6 生产者在数据发送过程中常见的异常
1.1 LeaderNotAvailableException:这个就是如果某台机器挂了,此时leader副本不可用,会导致你写入失败,要等待其他follower副本切换为leader副本之后,才能继续写入,此时可以重试发送即可。如果
说你平时重启kafka的broker进程,肯定会导致leader切换,一定会导致你写入报错,是LeaderNotAvailableException。
1.2 NotControllerException:这个也是同理,如果说Controller所在Broker挂了,那么此时会有问题,需要等待Controller重新选举,此时也是一样就是重试即可
3)NetworkException:网络异常,重试即可
我们之前配置了一个参数,retries,他会自动重试的,但是如果重试几次之后还是不行,就会提供Exception给我们来处理了。
重试机制:
参数:retries 默认值是3
参数:retry.backoff.ms 两次重试之间的时间间隔。
3.7 生产者发送消息重试带来问题
1.消息会重复
leader 切换需要重试,或者是网络延迟导致数据重复发送。但是已经成功了。
2.消息乱序:
重试导致消息的乱序。1,2,3 2,3,1
可以设置参数:
props.put(“max.in.flight.requests.per.connection”,1);
设置保证producer同一时间只能发送一条消息。
一个成功再写入写入另外一个。
3.8 ack判断是否发送成功
1.ack 参数详解:
request.required.acks=0;
只要请求发送出去,就算发送完了,不关心是否写成功。性能很好,可承受丢失
2.request.required.acks=1;
发送一条消息,当leader partition 写入成功之后,才算成功。
这种方式可能丢数据。
3.request.required.acked.acks=-1;
需要ISR(副本)列表里面,所有的副本写完之后,这条消息才算写成功。ISR:3 个副本,1leader partition 2 follow partition
kafka 服务端相关参数:
min.insync.replicas:1 如果不设置,默认是1
设置是2 如果我们这个ISR里面最少有两个副本。如果只有一个副本的话
往这个分区插入数据的时候会出错。
3.9 设计不丢数据方案
1.首先创建主题的时候,分区副本要大于等于2
2.request.required.acks=-1;
3.min.insync.replicas>=2