Kafka学习视频心得(二)kafka架构深入、API

1、Exactly Once

1)、引入幂等性

如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3H4okQEh-1612358822124)(../../AppData/Roaming/Typora/typora-user-images/image-20210203105337453.png)]

0.11版本的Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指Producer不论向Server发送多少次重复数据,Server端都只会持久化一条。幂等性结合At Least Once语义,就构成了Kafka的Exactly Once语义。即:

At Least Once + 幂等性 = Exactly Once

2)、如何启用?

要启用幂等性,只需要将Producer的参数中enable.idompotence设置为true即可。Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的Producer在初始化的时候会被分配一个PID,发往同一Partition的消息会附带Sequence Number。而Broker端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker只会持久化一条。

3)、缺点

PID重启就会变化,同时不同的Partition也具有不同主键,所以幂等性无法保证跨分区跨会话的Exactly Once。

2、消费方式

consumer采用pull(拉)模式从broker中读取数据

1)、为什么不用push方式?

push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。

2)、pull方式的不足之处以及解决方法

如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。

3、kafka消费者分区分配策略

1)、当consumer的数量比partition的数量小的时候怎么分配?

Kafka有两种分配策略,一是roundrobin,一是range。

2)、roundrobin策略

可以理解为发牌时候的按序分发(你一张我一张)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EOlYZe9-1612358822126)(../../AppData/Roaming/Typora/typora-user-images/image-20210203112102191.png)]

3)、range策略

也相当于发牌,但是提前看几张牌几个人分配好每个人对应的牌的数量(三个人,7张牌,第一个人三张后面两人一人两张,直接把三张给第一人,后面每人两张)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ABE4x40T-1612358822127)(../../AppData/Roaming/Typora/typora-user-images/image-20210203112244601.png)]

4、offset的维护

1)、引入

由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。

2)、解决

Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets。

补充:zookeeper毕竟是外部框架,而且存储在zookeeper不好优化,放在Kafka内方便优化(自己动手丰衣足食)

5、Kafka高效读写数据

1)、顺序写磁盘

Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。

2)、页面缓存(Pagecache)

①、定义

页缓存是操作系统实现的一种主要的磁盘缓存,以此用来减少对磁盘I/O的操作。具体来说,就是把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问。

②、pagecache具体操作

当一个进程准备读取磁盘上的文件内容时,操作系统会先查看待读取的数据所在的页(page)是否在页缓存(page cache)中,如果存在(命中)则直接返回数据,从而避免了对物理磁盘I/O操作;如果没有命中,则操作系统会向磁盘发起读取请示并将读取的数据页写入页缓存,之后再将数据返回进程。同样,如果一个进程需要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在,则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改过后的页也就变成了脏页,操作系统会在合适的时间把脏页中的数据写入磁盘,以操作数据的一致性。

③、联系kafka

Kafka数据持久化是直接持久化到Pagecache中

④、好处

1、 I/O Scheduler 会将连续的小块写组装成大块的物理写从而提高性能

2、I/O Scheduler 会尝试将一些写操作重新按顺序排好,从而减少磁盘头的移动时间

3、 充分利用所有空闲内存(非 JVM 内存)。如果使用应用层 Cache(即 JVM 堆内存),会增加 GC 负担

4、读操作可直接在 Page Cache 内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过 Page Cache)交换数据

5、如果进程重启,JVM 内的 Cache 会失效,但 Page Cache 仍然可用

尽管持久化到Pagecache上可能会造成宕机丢失数据的情况,但这可以被Kafka的Replication机制解决。如果为了保证这种情况下数据不丢失而强制将 Page Cache 中的数据 Flush 到磁盘,反而会降低性能。

3)、零复制技术

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBqJaYdy-1612358822128)(../../AppData/Roaming/Typora/typora-user-images/image-20210203142904122.png)]

①、传统IO流程

1、第一次:将磁盘文件,读取到操作系统内核缓冲区;
2、第二次:将内核缓冲区的数据,copy到application应用程序的buffer;
3、第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
4、第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。

②、kafka的做法

数据直接在内核完成输入和输出,不需要拷贝到用户空间再写出去。
kafka数据写入磁盘前,数据先写到进程的内存空间。

③、对比

思考传统IO方式,会注意到实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。

显然,第二次和第三次数据copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的意义。

④、零拷贝局限

零拷贝运用于读取磁盘文件后,不需要做其他处理,直接用网络发送出去。试想,如果读取磁盘的数据需要用程序进一步处理的话,必须要经过第二次和第三次数据copy,让应用程序在内存缓冲区处理

6、Zookeeper在Kafka中的作用

1)、Controller解释

Kafka集群中有一个broker会被选举为Controller,负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。

2)、partition的leader选举过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnE42k0m-1612358822130)(../../AppData/Roaming/Typora/typora-user-images/image-20210203151743029.png)]

①、brokers在启动的时候会在zookeeper里面注册一个临时节点(用来告知zookeeper),其中有一个broker会被选为Controller

②、Controller会监听图中ids目录下的子节点,每当我们新建立一个话题topic,Controller就负责分区、副本的存放。

③、对于每个话题的每个分区会在zookeeper的/brokers/topics/话题名/parttions/0/state目录下记录leader是谁,以及isr是谁,这件事由Controller来完成。

④、假设broker0挂了,ids里面0就会消失(临时节点),Controller就会监听此变化,并且选举出新的leader(如图中broker1),再更新state目录下的内容

7、Kafka事务

1)、Producer事务

事务的唯一标识(手起)Transaction ID,当启用事务以后,kafka就会开启一个内部话题Transaction state,这个话题负责存储Transaction ID、Producer ID(PID)、消息的状态连同信息(比如消息有100条我才写到50条这就是状态),以上的存储操作就是由Transaction Coordinator负责

Producer就是通过和Transaction Coordinator交互获得Transaction ID对应的任务状态。由于Transaction state存储了事务的状态,即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。

2)、Consumer事务

①、server对consumer几乎没有控制能力,consumer想回去拉就可以回去拉数据

②、回滚可能会失败,回滚的数据已经被kafka清理

8、Producer API

1)、消息发送流程

Kafka的Producer发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程——main线程和Sender线程,以及一个线程共享变量——RecordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtVLq9dd-1612358822131)(../../AppData/Roaming/Typora/typora-user-images/image-20210203192645979.png)]

batch.size:只有数据积累到batch.size之后,sender才会发送数据。

linger.ms:如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。

2)、异步发送API

①、导入依赖
<dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>1.0.0</version>
</dependency>
②、Producer.java
public class Producer {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1、实例化kafka集群

        Properties properties = new Properties();
        properties.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.setProperty("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.setProperty("acks", "all");
        properties.setProperty("bootstrap.servers", "test:9092");
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);

        //2、用集群对象发送是数据
        for (int i = 0; i < 10; i++) {
            Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>(
                            "first",
                            Integer.toString(i),
                            "Value" + i
                    ),
                    //回调函数
                    new Callback() {
                        /**
                         * 当我们的send收到服务器的ack以后,会调用onCompletion方法
                         * @param metadata 消息发送到那个分区,传递的元数据的返回
                         * @param exception 发送失败返回exception
                         */
                        @Override
                        public void onCompletion(RecordMetadata metadata, Exception exception) {
                            if (exception == null) {
                                System.out.println(metadata);
                            }
                        }
                    });
            RecordMetadata recordMetadata = future.get();//同步,不加就是异步
            System.out.println("发完了" + i + "条");
        }

        //3、关闭资源
        producer.close();
    }
}

9、Consumer API

1)、自动提交Offset的Consumer

①、consumer1.properties
key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
bootstrap.servers=test:9092
enable.auto.commit=true
group.id=test
auto.offset.reset=earliest
②、Consumer.java
public class Consumer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1、实例化consumer对象
        Properties properties = new Properties();
        properties.load(Consumer.class.getClassLoader().getResourceAsStream("consumer1.properties"));
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties);
        //2、用这个对象接收消息
            //Collections.singleton单例集合
        consumer.subscribe(Collections.singleton("first"));

        while(true){
            //从订阅的话题中拉取数据,后接拉取的时间(这边设置超过2s失败)
            ConsumerRecords<String, String> poll = consumer.poll(2000);

            if(poll.count()==0){
                Thread.sleep(100);
            }

            //消费拉取的数据
            for (ConsumerRecord<String, String> record : poll) {
                System.out.println(record);
            }
        }
        //3、关闭consumer
        //consumer.close();
    }
}

2)、手动提交Offset的Consumer

//consumer.commitSync();//同步提交
            consumer.commitAsync();//异步提交

确保消费和提交原子性,要么同时成功,要么同时失败
但是无论是异步还是同步还是有可能造成数据重读,因此要自定义保存offset

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友培

数据皆开源!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值