Kafka -基础篇

Kafka初识

Kafka是什么

Kafka是最初由LinkedIn公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统。

设计理念

  1. 低延迟:持久化消息、消费消息时间复杂度都为O(1)
  2. 高吞吐:普通机器也可以实现每秒发送10W条消息
  3. 水平扩展:broker、producer、consumer都支持在线水平扩展,
  4. 顺序性:每个partition内的消息被顺序消费

基本概念

  • Broker: Kafka集群包含若干个服务器,每个服务器视为一个broker
  • Topic: 发送到Kafka的消息都有一个唯一的类别,称为topic
  • Partition: 每个Topic会被分成若干个partition,每个partition对应一个文件夹,其存储了实际的消息,每条消息只会被存到一个partition中
  • Repication: 副本,每个分区都有若干副本,当分区leader宕机时,会从分区副本中选择一个成为新的leader
  • Producer: 消息的生产者
  • Consumer: 消息的消费者,一个消费者可以订阅若干个topic以便消费其中的消息
  • Controller: 也是一个broker,除了具有基本broker的功能外,还负责分区选举和故障切换,controller的工作机制会在下一章介绍
  • ISR(In Sync Replica): 每个分区包含一个ISR列表,里面都是和master数据保持一致的副本(包括master)

Kafka架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9JyWxlud-1617009556500)(https://tech-proxy.bytedance.net/tos/images/1616471663079_ba5d8a77b5ab14ac4b248114edb8fb6d.png)]

  1. producer直接和broker通信,broker中存有元数据信息(有多少个broker,每个topic有多少分区,每个分区的master是谁),这样就避免了producer和zk通信
  2. 每个broker都和zk通信
  3. consumer也直接和broker通信

Topic逻辑结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vs3PWGrd-1617009556503)(https://tech-proxy.bytedance.net/tos/images/1614600427026_e0ebccba6d7fc22b7c66e1d317ea0640.png)]

  • 一个topic可以包含多个partition,每个partition对应一个文件夹,而每个partition又可以包含多个segment,每个segment对应一个文件
  • 生产者以append-only的方式往partition写数据(顺序写盘的速率 ≈ 随机读内存速率),消费者消费时从低位开始消费,消息符合FIFO
  • 一个topic可以配置多个partition,可以存储任意多的数据
  • 每个partition都可以设置消息有效期,到期后,消息无论是否被消费都会被清除
  • 每条消息被发送到分区后都会指定一个offset,该offset在分区中递增

ZooKeeper简介

ZooKeeper是一个轻量级的分布式协调服务,目前kafka是强依赖于zk进行元数据管理、配置管理、集群管理等功能的。

Kafka中zooKeeper的存储结构如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFCkoMXf-1617009556504)(https://tech-proxy.bytedance.net/tos/images/1616665891208_bad1b0bfa1b20720fc20fe66a15e5ddc.png)]

生产者 Producer

Kafka消息结构

public class ProducerRecord<K, V> {

    private final String topic;//消息所属topic
    private final Integer partition;//消息所属分区
    private final Headers headers;//消息头
    private final K key;//如果partition没指定且key存在,kafka会根据key来计算partition
    private final V value;//消息体
    private final Long timestamp;//时间戳
    }

消息发送方式

  • 同步发送:发送消息后producer会一直阻塞等待broker的响应,如果发生可重试错误Kafka会自动重试,其底层也是调的异步发送。
  • 异步发送:异步发送消息,producer只发送消息,并不关心返回值,可以注册回调函数对异常情况进行处理。

这2种生产方式demo

发送确认

acks参数指定了一条消息被多少个分区副本同步后才能被认为写入成功,其取值情况如下:

  • acks=0,这种情况生产者不等待broker响应,不保证消息被同步到broker。这种模式下吞吐量最大,但丢消息的可能性最大。
  • acks=1,只要分区的master成功写入这条消息就会被认为写入成功。这种情况下不保证消息被同步到其他副本,同时也存在丢消息的可能性。
  • acks=all,必须等待在isr中的所有分区副本都成功写入这条消息才会被认为写入成功。这种情况下吞吐量最低,单丢消息的概率也最低。

分区器

在每条Kafka的消息中都包含消息所属分区,如果在发送时没有指定分区信息和key,kafka会使用默认的轮询(Round Robin)策略来得到分区信息。当然,我们也可以自定义分区策略,比如topic为test的消息统一放到最后一个分区。

public class CustomerParatitioner implements Partitioner {

    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        //这里通过主题名称得到该主题所有的分区信息
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);
        int numPartition = partitionInfos.size();
        if (numPartition <= 1) {
            return 0;
        }
        //后面进行逻辑判断返回哪个分区, 这里比如 key为test的 选择放入最后一个分区
        if (key.toString().equals("test")) {
            return partitionInfos.size() - 1;
        }

        //返回源码中默认的 按照原地址散列
        return (Math.abs(Utils.murmur2(keyBytes)) % (numPartition - 1));
    }
}

消费者 Consumer

消费者组 Consumer Group

每个消费者都从属于一个消费者组,一个消费者组中的所有消费者订阅的主题相同并且offset共享,每个消费者消费主题中一部分分区的消息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OaZJpgZt-1617009556506)(https://tech-proxy.bytedance.net/tos/images/1615357489200_5a955e4197039f43ba78060282193a9e.png)]

注意:这里consumer4是消费不到消息的,为什么?

  1. 保证分区内的消息被顺序消费
  2. 实现起来更为简单,不用考虑多个消费者同时消费同一个分区时的数据竞争问题

再均衡Rebalance

Consumer在启动时就会被指定消费的分区(由消费者协调器实现,下一篇介绍),如果消费者组中消费者数量发生变化就会触发分区重分配,会重新计算每个消费者所消费的分区,这就是再均衡。在这期间,消费者组对外不可用,不会消费消息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ajx6BSgF-1617009556509)(https://tech-proxy.bytedance.net/tos/images/1615722729325_a4d30535ef1841cc2c43e8f07af5ab63.png)]

Offset管理

Consumer通过提交offset来记录消费进度,kafka将消费者提交的offset存在一个叫做_consumer_offsets的topic中,所以consumer提交偏移量实际上就是往这个topic发消息,kafka支持3种提交offset的方式。

  • 自动提交

设置enable.auto.commit=true,kafka会每隔一段时间(默认5s)检查是否提交过偏移量,没有则会自动提交,该过程发生在拉取消息poll()中。

  • 同步提交
    设置enable.auto.commit=false,调用commitSync()可以手动同步提交offset,该方法会一直阻塞直到成功/异常/超时。

  • 异步提交
    设置enable.auto.commit=false,调用commitAsync()可以手动异步提交offset,该方法不会阻塞,但失败时不会重试。

为什么异步提交失败时不会重试?

第一次调用commitAsync()提交的offset为10,第二次调用commitAsync()提交的offset为20,此时第一次由于网络原因导致超时,而第二次提交成功。如果此时第一次请求重试则会覆盖第二次的提交

在提交offset之前发生再均衡,以上3种情况都会造成消息的重复消费,可以通过实现再均衡监听器解决。

    /**
     * 再均衡监听器
     */
    private static class HandleRebalance implements ConsumerRebalanceListener{

        /**
         * 方法会在再均衡开始之前和消费者停止读取消息之后被调用。如果在这里提交偏移量,下一个接管分区的消费者就知道该从哪里读取了。
         * @param partitions
         */
        @Override
        public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
            System.out.println("Lost partitions in rebalance.Committing current offsets:" + currentOffsets);
            consumer.commitSync(currentOffsets);
        }

        /**
         * 方法会在重新分配分区之后和消费者开始读取消息之前被调用。
         * @param partitions
         */
        @Override
        public void onPartitionsAssigned(Collection<TopicPartition> partitions) {

        }
    }

Kafka vs RocketMQ

性能对比

下图为阿里官方对Kafka和RocketMQ在不同个数的topic情况下得到的压测报告,可以看出在topic个数较小时Kafka性能是优于RocketMQ的,但随着topic个数增加,Kafka性能大打折扣,远远不如RocketMQ。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJkLcR8k-1617009556511)(https://tech-proxy.bytedance.net/tos/images/1615345728700_55f226a0ea67c061d81d3b905b382980.png)]

思考:为什么在少量topic时Kafka性能优于RocketMQ,而topic个数多时结果又相反呢?

  1. topic较少时:由于Kafka在发送端做了批处理,producer会将多条消息封装成一个批次,然后发给broker。
  2. topic较多时:Kafka的文件模型为一个partition对应一个文件,而RocketMQ是所有queue共享一个CommitLog。所以在多topic下,Kafka消息的分散落盘策略会导致磁盘IO竞争成为瓶颈,而RocketMQ则是顺序写磁盘,速度很快。

参考链接

1.BootCamp Kafka视频

2.kafka官网

你可能感兴趣的:(golang,java)