kafka的使用及工作原理

记录Kafka的使用

什么是kafka

  • 定义

  • Kafka是分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域

  • 消息队列

  • 削峰:高峰期将任务缓存在消息队列中,持续处理

  • 解耦:不需要两边的处理同时在线

  • 可恢复性:当系统一部分组件失效时,不会影响到整个系统

  • 缓冲:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息速度不一致的问题

  • 异步通信:允许用户将一个消息放入队列,但并不立即处理它

  • 消息队列的模式

  • 1.点对点模式

特点:一对一,消费者主动获取消息,消息被消费后清除

消息生产者可以对应多个消费者,但是消息只能被一个消费者消费
  • 2.发布/订阅模式
特点:一对多,生产者推送数据,消费者消费数据之后不会清除消息

消息生产者将消息发布到topic中,同时有多个消费者消费该消息

发布/订阅模式又分为
MQ主动的推送模式
消费者主动的获取模式
  • kafka基础架构
  • 1.Producer:消息生产者,就是向kafka broker发消息的客户端
  • 2.Consumer:消息消费者,就是向kafka broker拉取消息的客户端
  • 3.Consumer Group:多个Consumer组成一个group,消费者组内的每一个消费者负责消费不同分区的数据,一个分区只能由一个消费者消费,消费者组之间互不影响.消费者组是逻辑上的一个订阅者
  • 4.Broker:一台kafka服务器就是一个broker,一个集群由多个broker组成.一个broker可以容纳多个topic
  • 5.Topic:一个主题,生产者和消费者面向的都是一个主题
  • 6.Partition:一个TOPIC可以分布在不同的BROKER中,由多个Partition组成,每个partition都是一个有序的队列
  • 7.Replica:副本,Broker中维护一个TOPIC的某个partition(Leader)同时还维护了该TOPIC其他partition的follower
  • 8.Leader:每个Topic都由一个若干个Partition组成,而每一个Partition又有1到brokers数量的副本,其中只有主副本(Leader)才能对外提供服务,其他的副本(Follower)只是用来保存数据
  • 9.Follower:每个分区多个副本中的从,主要用于数据备份.当Leader发生故障时,某个follower会成为新的leader
  • 10.注册中心,kafka依赖于zookeeper,作为注册中心

kafka快速入门

  • 安装
    https://www.cnblogs.com/zikai/p/9627736.html
  • 常用命令(命令行)
  • 1.kafka-server-start:启动kafka
通常启动kafka需要指定配置文件
bin/kafka-server-start.sh -daemon config/server.properties

这里使用守护进程启动,使用jps指令可以看到正在执行的kafka
  • 2.kafka-server-stop:停止kafuka服务
bin/kafka-server-stop.sh
  • 3.kafka-topics :主题相关操作
1.kafka-topics --list:查看当前所有主题
  ./kafka-topics.sh --list --zookeeper 7.223.145.184:2181 注意必须跟zk的地址
2.kafka-topics --create:创建主题
  ./kafka-topics.sh --create --zookeeper 7.223.145.184:2181 --topic first --partitions 2 --replication-factor 1
  创建一个名称为first的主题,该主题有2个partition,和1个副本
  
  副本数量最多和Broker数量一样.副本都会存在kafka所在的磁盘log.dirs配置的位置
3.kafka-topics --delete:删除主题
  ./kafka-topics.sh --delete --zookeeper 7.223.145.184:2181 --topic first
4.kafka-topics --describe:查看主题详情
  ./kafka-topics.sh --describe --zookeeper 7.223.145.184:2181 --topic first
  • 4.kafka-console-producer: 生产者
./kafka-console-producer.sh --topic first --broker-list 7.223.143.235:9092 
创建一个生产者.链接到我们的集群(7.223.143.235:9092,此时我只有一个kafka,集群也是一样的,只需要链接到任意一台)
此时我们就可以输入消息了
  • 5.kafka-console-consumer: 消费者
1.消费
./kafka-console-consumer.sh --topic first --bootstrap-server 7.223.143.235:9092
./kafka-console-consumer.sh --topic first --zookeeper 7.223.145.184:2181(上古版本可用,新版本已不支持)
为了能够实现续传,kafka记录了某个消费者group对某个主题的消费情况(消费到哪里了?)
kafka在上古时期是通过zookeeper来存储偏移量的,存放在/consumers/[group_id]/offsets/[topic]/[broker_id-part_id]节点下
新版本的kafka将这些消息存在kafka集群的consumer主题中,不再依赖zookeeper,该主题默认是50个分区1个副本
2.从头消费
./kafka-console-consumer.sh --topic first --bootstrap-server 7.223.143.235:9092 --from-beginning
我们利用--from-beginning参数可以从头消费

kafka架构深入

  • kafka架构及工作过程
  • kafka的架构如下图,作如下假设
    在这里插入图片描述
  • 1.当前的topic为A,有3个partition(0,1,2),Replica数为2,每个broker保存的副本如图所示
  • 2.每个partition当中存在6/4/5条消息,途中的编号为消息的offset,此时我们的producer一共产生了15条消息,可见,每个partition都有单独的offset编号,从而我们也可以看到,kafka不能保证全局有序
  • 3.Follower会自动备份Leader中的消息,这就是kafka的容灾策略
  • 4.Consumer Group:正如图所示,每一个消费者对应一个分区,消费者每消费一条数据,就会记录下对应已消费的offset值(同样也是partition隔离的),以便于出错时可以从上次的位置继续消费
根据Kafka的规定
1.每一个partition只能被同一个组中的一个consumer消费
2.同一个组中的consumer可以消费多个partition
所以最理想的情况是一个partition对应一个consumer(同一个consumer group中),这就是我们的分区依据
比如我们生产的速度是10kB/s,消费者性能的极限速度是1kB/s.
那么我们需要10台consumer才能稳定消费,此时我们可以将topic设置为10个分区,同时将10个consumer组成一个consumer group

如果我们想要多个consumer消费一个partition,怎么办呢?
我们可以设定多个consumer group,来消费同一个partition
https://www.cnblogs.com/sa-dan/p/8080197.html
  • kafka文件存储机制
    文件存储
  • 由于生产者产生的消息会不断追加到log文件末尾,为了有效防止log文件过大导致数据定位效率低下,Kafka引入了分片索引机制,将每个partition分为多个segment(默认是1GB),每个segment对应两个文件"xx.index"和"xx.log".其中前缀是当前segment的第一条消息的offset.
  • 如下图所示,index存放的内容是每条消息offset和消息在Log文件中存储位置的映射关系
    在这里插入图片描述
  • kafka分区操作
  • 本节阐明生产者如何让生产的消息选择分区(Java)
  • 1.生产者可以直接指明选择的分区
    ProducerRecord(@NotNull String topic,Integer partition,String key,String value)
    
  • 2.如果值是key,value形式的,可以使用key的hash与分去数取余
    ProducerRecord(@NotNull String topic,String key,String value)
    
  • 3.如果只有value,则通过轮询
    ProducerRecord(@NotNull String topic,Integer partition,String value)
    
  • 数据可靠性
  • 本节阐明如何确保生产者的消息成功传给kafka,保证数据不丢失
  • 1.topic的每个partition收到produicer发送的数据之后,都需要向producer发送ack,如果producer收到ack,就会进行下一轮的发送,否则重新发送数据
  • 2.ISR(in-sync relplica)
    Kafka的同步机制
    kafka在发送ack之前,要求全部的follower都同步完成
    这种方案的缺点是延迟高.优点是容灾需要的节点少(选举新leader的时候,容忍n节点故障,需要N+1个副本)
    
    zk选用的方案是半数机制,这种机制的优点是延迟低,但容灾需要的节点多(选举新leader,容忍N节点故障,需要2*N+1个副本)
    
    为了防止某个follower长时间不响应导致同步困难,kafka维护了一个ISR用来解决这个问题
    Leader维护了一个动态的ISR
    当ISR中的follower长时间未向leader同步,则该follower将被踢出ISR
    该时长由replica.lag.time.max.ms决定
    Leader发生故障后会从ISR中选举新的Leader
    
  • 3.ack应答机制
  • kafka有三种ack机制
  • 0:接收到信息直接ack
    效率最高,容错率最低
    
  • 1:接收到信息leader落地完成后ack
    效率高.但也容易丢失数据
    
  • -1:所有isr中的副本同步完成后ack
    效率最低.
    在极端情况下也会丢失数据:isr中只有leader并且down
    在某些情况下会造成数据重复:数据同步完成后还未返回ack的时候leader挂掉
    
  • 消费数据一致性
  • 由于kafka有多种ack机制,在某些机制下同步还没有完成就接收到了新的数据,有可能出现数据不同步,此时如何对消费者保证数据的一致性
    在这里插入图片描述
  • 可见如果此时leader挂掉了,选择了follower1,则此时最大的OFFSET为15,会导致数据不一致
  • 为此kafka引入了两个概念HW和LEO,consumer可以收到的OFFSET为HW
LEO:Log End Offset,每个副本中最大的OFFSET
HW:High Watermark,类似于木桶效应,ISR中最小的LEO
  • 基于HW的故障处理(保证partition中的数据一致性)
1.follower故障:follwer从故障恢复后,会读取本地次胖记录的上次的HW,并将Log文件高于HW的部分丢弃,
			  从HW开始向leader重新同步,当follower的LEO>=该partition的HW时,Follwer就可以重新加入ISR了
2.leader故障:leader发生故障后,会从ISR中选出一个新的leader,为保证多个副本之间的数据一致性,
			 其余的follower会将各自的log文件高于HW的部分截掉,然后从新的leader同步数据
  • Exactly Once
  • 前面介绍了不同的ack机制,都存在不同的问题
ACK 0: at most once -> 数据可能丢失
ACK -1: at last once -> 数据可能重复
  • kafka在0.11版本之后解决了数据重复的问题
幂等性开关开启(Producer的参数enable.idompotence = true)后,该producer在初始化时会被分配一个PID
发往同一个Partition的消息会附带Sequence Number,Broker会对<PID,Partition,SeqNumber>作缓存这就是Kafka去重的依据
注意:重启PID会变化,同时kafka只能保证同一个partition中的exactly once
消费者
  • 消费方式
  • 消费者采用pull(拉取)的方式从broker中获取数据
pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据
针对这一点,kafka在消费数据时会传入一个市场参数(timeout)
如果没有数据可以消费,consumer会等待一段时间之后再返回,这段市场即为timeout
  • 分区分配策略
  • 一个consumer group中有多个consumer,一个topic有多个partition,所以必然会涉及到partiton的分配问题,即决定某个partition由哪个consumer消费’’
  • Kafka有两种分配策略,一是RoundRobin,一是Range

== 加图==

  • 1.RoundRobin:轮询分配.在轮询策略中,会将消费者组监听的所有主题所有partition进行排序后轮询交付给consumber
优点:可以使得所有的consumer监听的partition个数差别最小
缺点:无法保证消费者消费的主题
  • 2.Range(默认):范围分配是按照单个主题来划分
优点:保证消费者组的每一个消费者都能消费所有的主题
缺点:消费者监听partition个数不对等

Kafka API

Producer API
  • 消息发送流程
  • kafka的Producer发送消息采用的是异步发送的方式.在消息发送的过程中.涉及到两个线程–main和sender线程.以及两个线程的共享变量–RecordAccumulator,sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker

== 加图==

  • 相关参数
1.batch size:只有数据累积到batch.size之后,sender才会发送数据
2.linger.ms:如果数据迟迟未达到bath.size,sender等待linger.time之后就会发送数据
3.我们从ProducerConfig类中可以找到所有的配置
  • 普通生产者
public class MyProducer {
    public static void main(String[] args) {
        //创建生产者的配置信息
        Properties properties = new Properties();
        properties.put("bootstrap.servers","7.223.143.235:9092");
        //ack应答级别-1
        properties.put("ack","all");
        //重复次数
        properties.put("retries",3);
        //批次大小 16kb
        properties.put("batch.size",16384);
        //等待时间
        properties.put("linger.ms",1);
        //RecordAccumulator缓冲区大小
        properties.put("buffer.memory",33554432);
        //序列化类
        properties.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
        //创建生产者
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<String,String>("third","value--"+i));
            System.out.println(i);
        }
        //关闭资源
        producer.close();
    }
}
  • 带回调函数的生产者
public class CallBackProducer {
    public static void main(String[] args) {
        //创建生产者的配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "7.223.143.235:9092");
        //序列化类
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //创建生产者
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<String, String>("third", "value--" + i), (metadata, exception) -> {
                if (exception == null) {
                    System.out.println(metadata.partition() + "--" + metadata.offset());
                } else {
                    exception.printStackTrace();
                }
            });
        }
        //关闭资源
        producer.close();
    }
}
  • 生产者分区策略
public class CallBackProducer {
    public static void main(String[] args) {
        //创建生产者的配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "7.223.143.235:9092");
        //序列化类
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //创建生产者
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //发送数据
        for (int i = 0; i < 10; i++) {
            //指定内容都发送到0号分区
            producer.send(new ProducerRecord<String, String>("third", 0,"key","value--" + i), (metadata, exception) -> {
                if (exception == null) {
                    System.out.println(metadata.partition() + "--" + metadata.offset());
                } else {
                    exception.printStackTrace();
                }
            });
            //通过key的hash值确定分区
			 producer.send(new ProducerRecord<String, String>("third", i%3+"","value--" + i), (metadata, exception) -> {
                if (exception == null) {
                    System.out.println(metadata.partition() + "--" + metadata.offset());
                } else {
                    exception.printStackTrace();
                }
            });
        }
        //关闭资源
        producer.close();
    }
}
  • 自定义分区器
public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        return key.toString().hashCode() % cluster.partitionCountForTopic(topic);
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

public class PartitionProducer {
    public static void main(String[] args) {
        //创建生产者的配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "7.223.143.235:9092");
        //序列化类
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //配置分区器
        properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"xx.xx.xx.MyProducer");
        //创建生产者
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<String, String>("third", "value--" + i))
        }
        //关闭资源
        producer.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值