kafka入门

一、简介
1.是什么
是一个分布式、支持分区的(partition)、多副本的(replication),基于zookeeper协调的分布式消息系统,可以实时的处理大量数据.

2.有哪些主流的消息队列(中间件)
RabbitMQ:由Erlang(二郎)语言编写。吞吐量比较低,不容易进一步开发扩展。
RocketMQ:由java编写,阿里开发,社区活跃度低,万一不维护,需要自己公司研发。
Redis:用作消息队列时,数据量大小在10k以内速度快,数据量大时会非常慢
Kafka:Apache开发,由Scala和java编写,适合大数据实时计算以及日志收集
3.为什么要使用消息队列
主要用来缓冲任务和削峰:上游数据有时会突发流量,下游可能扛不住,或者下游没有足够多的机器来保证处理,此时kafka在中间可以起到一个缓冲作用,把消息暂存在kafka集群中,下游服务就可以按照自己的节奏慢慢处理任务。

4.消息队列的应用场景
用户管理中,当成功写入数据库后的回调信息中要作两件事:

发送注册邮件
发送注册短信
如果使用消息队列,则在收到异步通知后就直接响应用户,把后两件事放在队列中慢慢处理。

串行处理任务

并行处理任务(多线程问题)

kafka消息队列处理

  1. kafka的分布式实现
    NameServer在kafka中使用的是zookeeper

6.Kafka特性
解耦、高吞吐量、低延迟、高并发、容错性、可扩展性、持久性和可靠性

解耦:使用kafka后,任务的处理者(consumer)与任务的发布者(producer)之间没有依赖关系。

高吞吐量、低延迟:kafka每秒可处理几十万条消息,延迟可低到几毫秒。每个topic可以分多个partition,consumer group对分区可并行读取

可扩展性:kafka集群支持热扩展

持久性和可靠性:

消息被持久化到本地磁盘,并支持数据备份防止数据丢失。

容错性:允许集群中节点失败,只要还剩下一个就能正常工作

高并发:支持数千个客户端的读写

7.消息的分类
点对点:一个队列可以有多个消费者一起消费,但一个消息只能被一个消费者消费。

发布与订阅

消息被持久化到一个topic中,消费者可消费该topic中所有数据,同一条数据可被多个消费者消费,数据被消费后不会删除。

二、kafka整体架构
1.kafka由哪些组件构成

1.1 topic:
是什么?就是一堆消息,由多个分区构成。

分区partition,为什么要分区:

从producer角度看,分区分布在不同broker上,方便容量扩展,同时也提高吞吐量和负载均衡
从consumer角度看,一个组内的某个消费者只能消费一个分区,分区后可以提高并发量,效率大提高。但要求组内消费者数量不能大于topic的分区数
副本replication

副本是什么?

leader副本:用来提供读写功能,一个分区只有一个leader副本

follower副本:只是被动地备份leader中数据,不提供读写功能,作用是为了提高分区的可用性

1.2 producer:
向topic中发布消息

1.3 consumer:
订阅topic中消息,并从topic中读取和处理消息

1.4 broker:
管理topic中消息存储的服务器

2 . ISR和AR是什么,ISR的伸缩又指什么?
ISR:In-sync Replicas(副本同步队列),一个分区中,包含了leader和所有与leader保持同步的follower的id,该队列由控制器和leader维护,如果follower从leader中同步时间超过阈值,就会被从ISR中踢出,并把该follower的id存入OSR(Outof-sync replicas)列表中

AR是什么?

AR=ISR+OSR

ISR伸缩:follower副本跟leader同步超时会ISR中移除,当follower同步了所有leader数据后又加入ISR中

3.控制器是什么?如何选举的
就是一个Broker,集群中第一个启动的broker会通过在zookeeper中创建临时节点/controller来试图让自己成为控制器,其他broker会在该节点消失时收到通知,然后分别再向zk中写/controller节点,只有一个能成功,其他节点只能监听该节点

作用:监听ids变化从而实现下面两个功能:

topic的分区副本分配:一个topic为了实现负载均衡,会被分成多个分区,这些分区信息及与broker的对应关系由controller维护

分区的leader选举

4.分区leader选举?
某个作为leader的broker挂了,则controller会把其从ISR中移除,再从ISR列表中找跟当前leader保持最高同步的副本作为leader,如果都保持了完全同步,则按顺序从前向后选。

三、环境搭建
1.kafka安装
网址下载软件:http://kafka.apache.org/downloads.html
百度链接:https://pan.baidu.com/s/1cFvOG7wfZYOwwYO2bHqang
提取码:vpho
解压:tar -zxvf kafka/kafka.tar.gz -C /opt/module/

在kafka目录中创建目录:mkdir logs,用来存放日志和消息数据

编辑配置文件:vim config/server.properties

broker.id=0
delete.topic.enable=true删除注释,以便可以删除topic
log.dirs=/opt/module/kafka/logs
zookeeper.connect=192.168.184.100:2181
listeners = PLAINTEXT://192.168.184.100:9092
host.name=192.168.184.100,这里的ip是当前虚拟机的ip
克隆两台虚拟机并修改ip为101和102

https://blog.csdn.net/wlxs32/article/details/105232268,注意要删除每台虚拟机kafka内部的logs目录

2.启动
1.启动zookeeper
/opt/module/zookeeper-3.4.10/bin/zkServer.sh start

2.启动与关闭kafka服务
在每台虚拟机上都执行下面命令

前台运行:/opt/module/kafka/bin/kafka-server-start.sh /opt/module/kafka/config/server.properties
后台运行:/opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties
通过jps命令查看java进程,如果看到kafka表示启动成功
关闭服务:
netstat -alnp | grep 9092,kill -9 进程号
bin/kafka-server-stop.sh,如果权限不足,只可以chmod 777 kafka-server-stop.sh
四、shell命令使用
关闭:/opt/module/kafka/bin/kafka-server-stop.sh

启动:/opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties

1.创建topic命令
/opt/module/kafka/bin/kafka-topics.sh —create —zookeeper 192.168.184.100:2181 —partitions 2 —replication-factor 2 —topic first

会在logs目录中创建topic,分别为first-0和first-1

2.查看集群中有哪些topic
/opt/module/kafka/bin/kafka-topics.sh —list —zookeeper 192.168.184.100:2181

3.查看指定topic的详细信息
/opt/module/kafka/bin/kafka-topics.sh —describe —zookeeper 192.168.184.100:2181 —topic first

没有zookeeper可以使用kafka吗?zk有什么用

管理集群broker的上下线,如broker上线,会把brokerId写到zk的/brokers/ids节点下,下线就从zk中删除

controller选举:kafka中某个broker会被zk选举为controller

controller有什么用?

监听ids变化从而实现下面两个功能:

topic的分区副本分配:一个topic为了实现负载均衡,会被分成多个分区,这些分区信息及与broker的对应关系由controller维护

分区的leader选举

4.删除topic
/opt/module/kafka/bin/kafka-topics.sh —delete —zookeeper 192.168.184.100:2181 —topic first

注意:必须所有分区所在的主机kafka服务处于开启状态才能正常完全删除。

5.修改一个topic的分区数
bin/kafka-topics.sh —zookeeper 192.168.184.100:2181 —alter —partitions 3 —topic first

注意:分区数只能增加不能减少

  1. producer客户端连接
    bin/kafka-console-producer.sh —broker-list 192.168.184.101:9092 —topic first

7.consumer客户端连接
旧版本用法:bin/kafka-console-consumer.sh —zookeeper 192.168.184.100:2181 —from-beginning —topic first

问题:offset的作用?

每个消息都有一个顺序ID号,叫偏移量offset,用来唯一地识别分区中的每条消息

新版本用法

bin/kafka-console-consumer.sh —bootstrap-server 192.168.184.102:9092 —from-beginning —topic first

offset的值不再保存到zk中,而是保存在一个叫作__consumer_offsets的topic中,可通过命令bin/kafka-topics.sh —list —zookeeper 192.168.184.100:2181查看

五、数据流程
1.生产数据流程
producer从broker-list中获取某一partition的leader

如zookeeper上获取0号分区的副本信息:get /brokers/topics/first/partitions/0/state

producer把消息发送给某一个分区的leader

leader将消息写入本地logs目录中

该分区的follwer从leader中pull消息,并写入follower本地logs,然后再向leader发送ack(acknowledge character)

leader收到所有follower的ack后再向producer发送ack

2.消费数据流程
采用pull模式消费消息,由消费者自己记录消费状态(自已把offset写入zk或kafka中),每个消费者互相独立的顺序读取每个分区的消息。如果broker没数据,则会有一个超时等待时间,过了这段时间再返回。

分区消费

组内某个消费者只能同时消费一个分区,如果分区数大于组内消费者,则默认采用轮询方式依次消费数据。

  1. 数据持久化
    1.存储方式:
    topic存储在一个或多个partition中,每个分区对应一个文件夹,用来放消息和索引文件。

2.数据删除策略
无论消息是否被消费,kafka都会保留所有消息,有两种策略可以删除旧数据

基于时间:log.retention.hours=168,默认采用这种方式(7天)
基于大小:log.retention.bytes=1073741824,1G,如果分区大小超过1G就会在适当时机把一最老的segment数据段删除
3.数据的可靠性如何保证?
ack为all的前提下采用幂等机制

通过控制丢失率、重复率和副本数据的一致性来实现。

ack的不同级别(3个)保证控制数据的丢失率和重复率

0:生产者将数据发送出去后就不管了,不去等待任何回应,好处是数据不会重复,效率高,但不能保证数据一定能成功写入

1:数据发送到leader,并成功写入leader的logs,但不会等待成功写入follower就会响应acks. 数据可能重复

如果leader在响应后,同步到follower前leader挂了,则会丢失数据。

all:会等待leader和所有副本成功写入,才响应acks,否则就重发。

好处是数据绝对不丢失,缺点是会重复。

为什么会重复?

如果数据已写入全部副本,但在响应producer前,leader挂了,那么producer就认为写入失败,就要重写。

如何在kafka内去除重复?

用acks=all和幂等机制,也就是将enable.indempotence设置为true就能实现。
实现原理:kafka会额外为producer分配一唯一的id,叫作pid,producer在发送消息时,生成消息的id(pid+消息的序列号),leader在接收消息时会把每条消息的id存入缓存中,并对新接收的消息判断是否是重复的id,如果重复就丢弃。
如何保证副本数据的一致性

通过LEO和HW保证副本数据的一致性。

LEO:标识当前分区中下一条待写入消息的offset

HW:高水位,实际上就是ISR中所有broker的LEO最小值,消费者只能获取HW这个offset之前的消息。

hw有什么用?

follower挂掉后,被移出isr,恢复后获取挂掉前的hw,把副本中所有大于等于这个hw的数据清除,从leader同步数据。
为什么要清除hw之后数据?因为follower恢复过程中,leader可能挂掉,并重新选举了新leader,而这个leader的LEO可能比较低,且随后又接收到一些新数据,该follower数据如果不删除一部分,就无法确定从哪个位置同步数据。
leader挂掉,会从ISR中选出一个新的leader之后,为保证多个副本之间的数据一致性,其余(不包括leader)的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。
4.写数据的高效性?
效率很高,因为采用顺序写,也就是在一个连续空间写数据,所以比写内存效率要高

5.消费者获取消息的高效性如何保证
分区:在消费者组中有多个消费者并发读取不同的分区数据

分段:将数据文件分段,比如100条消息,它们的offset是从0到99,假设将数据文件分成5段,第一段为0-19号消息,其文件名为0.log,第二段20-39,其文件名20.log,以此类推。

稀疏索引

为每个小文件中的部分消息建立索引文件,名字跟分段文件名相同,后缀为.index,其内容有两部分:

(1)部分消息的相对offset

(2)部分消息的poistion:是相当于文件首地址的物理偏移量。

6.producer发送消息到哪个分区?分区选择的原则是什么?
指定分区器:发送到分区器指定的分区中,p.put(“partitioner.class”,”xxx”)

没指定分区器,但指定了key,就会根据key的hash值跟分区数取模得到结果,就是要发送的分区new ProducerRecord<String, String>(“first”,”abc”,Hello” + i),这里abc就是key

key和分区器都没指定,则默认采用轮询(旧版本)决定发送到哪个分区。

配置文件

org.apache.kafka kafka_2.12 0.11.0.3 #cfg.properties bootstrap.servers=192.168.184.100:9092 #一批数据的大小 batch.size=16384 #accumulator缓冲区大小 buffer.memory=33554432 #提交延时,当达到指定时间,即便是accumulator缓存不满也会提交数据 linger.ms=1 #生产者需要用序列化器(Serializer)将key和value序列化成字节数组才可以将消息传入Kafka。 #消费者需要用反序列化器(Deserializer)把从Kafka中收到的字节数组转化成相应的对象 #指定kv的序列化器的类型,值为其class路径。 key.serializer=org.apache.kafka.common.serialization.StringSerializer value.serializer=org.apache.kafka.common.serialization.StringSerializer partitioner.class=hy.MyPartition retries=0 7. 生产者拦截器 用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求

如何实现?

定义类并实现接口:ProducerInterceptor

public class MyInterCeptor implements ProducerInterceptor<String,String>{
long sucNum;
long errNum;
//producer发送前会先调用该方法
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> r) {
return new ProducerRecord<String,String>(r.topic(),r.partition(),r.timestamp(),r.key(),System.currentTimeMillis()+r.value(),r.headers());
}
//在kafka响应ack时会调用
public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {
if(e==null){
sucNum++;
}else{
errNum++;
}
}
//kfka的Producer关闭时会调用
public void close() {
System.out.println(“本次共成功发送:”+sucNum+",失败:"+errNum);
}
//获取配置信息时调用
public void configure(Map<String, ?> map) {
System.out.println(map);
}
}
添加拦截器到配置文件中。

p.put(“interceptor.classes”,“hzn.MyInterCeptor”);
8.消费者
public class MyConsumer {
public static void main(String[] args) {
Properties p=new Properties();
p.put(“bootstrap.servers”,“192.168.184.100:9092”);
p.put(“group.id”,“gr_01”);
p.put(“enable.auto.commit”,“false”);//关闭自动提交
p.put(“key.deserializer”, StringDeserializer.class.getName());
p.put(“value.deserializer”, StringDeserializer.class.getName());
p.put(“auto.offset.reset”,“earliest”);//从头开始消费
KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>§;
while(true){
consumer.subscribe(Arrays.asList(“first”));
ConsumerRecords<String,String> cr=consumer.poll(100);
for(ConsumerRecord<String,String> r:cr){
System.out.println(“主题:”+r.topic()+",所属分区:"+r.partition()+",内容:"+r.value());
}
//手动提交
consumer.commitAsync();
}
/*
*异步:commitAsync,提交后就结束,不阻塞
*同步:commitSync,提交后会等待kafka确认,在收到确认前会阻塞
*/
}
}
9.分区选择
public class MyPartition implements Partitioner{
Random random=new Random();
int p=-1;
@Override
public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
System.out.println(“s=”+s);
int part=random.nextInt(2);
//return part;
p++;
if(p2){
p=0;
}
return p;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
10.生产者
public class MyProducer {
public static void main(String[] args) throws Exception{
Properties p = new Properties();
//p.put(“bootstrap.servers”,“192.168.184.100:9092”);
p.put(“acks”,“all”);
p.put(“enable.indempotence”,true);//幂等机制
p.load(MyProducer.class.getClassLoader().getResourceAsStream(“cfg.properties”));
Producer<String, String> producer = new KafkaProducer<String, String>§;
for (int i = 0; i < 10; i++) {
//producer.send(new ProducerRecord<String, String>(“first”,“hello_”+i));
//producer.send(new ProducerRecord<String, String>(“first”,“abc”,“hello_”+i));
producer.send(new ProducerRecord<String, String>(“first”, “abc”, “hello_” + i), (m, e) -> {
//收到ack时调用,发送失败且不再重发也会调用该方法
if(e
null){
//发送成功
System.out.println(“分区:”+m.partition()+",offset:"+m.offset());
}else {
e.printStackTrace();
System.out.println(“发送失败”);
}
}).get();//注意:调用get的后果是让发送方法阻塞;当收到ack并回调就解除阻塞
}
producer.close();
}
}
六、面试题
1.kafka是怎么体现消息顺序性的?
在一个分区内能过offset来维护顺序性,不同分区无法保证。

2.kafka新建的分区会在哪个目录中创建?
在配置文件指定的logs目录下创建:first-0,first-1

3.kafka中的分区器、序列化器、拦截器是否了解?之间的顺序是什么?
kafka在发送消息前先经过拦截器处理
序列化器转换为字节数组
分区器指定分区
4.什么情况会造成重复消费?什么情况会造成漏消费?
重复消费?

生产者:ack=all时生产者会重复发消息会重复消费,可通过幂等机制解决
消费者:消费者采用先消费后提交offset的方式,如果在消费结束后提交offset时,失败了,则会重复消费同一条记录。可通过同步提交offset的方式解决。
漏消费?

生产者:ack=0或1时,kafka在生产者发送了数据后可能没存储成功,从而漏消费。
消费者:消费者采用先提交offset,后消费,可能会漏消费。可先消费后提交解决
5.kafka内部有topic吗?有什么用?
__consumer_offsets,用来记录所有topic的所有分区被消费者消费的offset

6.如何保证消息只能被消费一次?
producer端

ack=all

p.put(“enable.indempotence”,true);//幂等机制
consumer端
关闭自动提交功能
每消费一次数据后手动提交
标签:kafka
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明,KuangStudy,以学为伴,一生相伴!

本文链接:https://www.kuangstudy.com/bbs/1350958803645935618

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值