apache kafka_实时构建:使用Apache Kafka进行大数据消息传递,第2部分

apache kafka

在JavaWorld对Apache Kafka的介绍的上部分,您使用Kafka开发了几个小型生产者/消费者应用程序。 通过这些练习,您应该熟悉Apache Kafka消息传递系统的基础知识。 在第二部分中,您将学习如何使用分区来水平分布负载和水平扩展应用程序,每天处理多达数百万条消息。 您还将学习Kafka如何使用消息偏移量来跟踪和管理复杂的消息处理,以及如何在用户失败时如何保护Apache Kafka消息系统不出现故障。 我们将在第1部分中针对发布-订阅和点对点用例开发示例应用程序

Apache Kafka中的分区

Kafka中的主题可以细分为多个分区。 例如,在创建名为Demo的主题时,您可以将其配置为具有三个分区。 服务器将创建三个日志文件,每个演示分区一个。 生产者向主题发布消息时,它将为该消息分配一个分区ID。 然后,服务器会将消息仅附加到该分区的日志文件中。

如果随后启动了两个使用者,则服务器可能将分区1和2分配给第一个使用者,并将分区3分配给第二个使用者。 每个使用者只能从其分配的分区中读取。 您可以在图1中看到为三个分区配置的Demo主题。

Apache Kafka中的分区主题

图1. Apache Kafka中的一个分区主题

为了扩展此方案,请想象一个Kafka集群,其中有两个代理,它们位于两台计算机中。 对演示主题进行分区时,可以将其配置为具有两个分区和两个副本。 对于这种类型的配置,Kafka服务器会将两个分区分配给集群中的两个代理。 每个经纪人将成为分区之一的负责人。

生产者发布消息时,它将转到分区负责人。 领导者将接收消息并将其附加到本地计算机上的日志文件中。 第二个代理将将该提交日志被动复制到其自己的计算机上。 如果分区负责人发生故障,则第二个代理将成为新的负责人,并开始为客户请求提供服务。 同样,当使用者向分区发送请求时,该请求将首先发送给分区负责人,分区负责人将返回所请求的消息。

分区的好处

考虑对基于Kafka的邮件系统进行分区的好处:

  1. 可伸缩性 :在只有一个分区的系统中,发布到主题的消息存储在一个日志文件中,该日志文件位于一台计算机上。 主题消息的数量必须适合单个提交日志文件,并且存储的消息大小不能超过该计算机的磁盘空间。 通过对主题进行分区,您可以通过将消息存储在群集中的不同计算机上来扩展系统。 例如,如果要为Demo主题存储30 GB的消息,则可以构建一个由三台计算机组成的Kafka群集,每台计算机具有10 GB的磁盘空间。 然后,您可以将主题配置为具有三个分区。
  2. 服务器负载平衡 :具有多个分区,使您可以在代理之间分布消息请求。 例如,如果您的主题每秒处理100万条消息,则可以将其划分为100个分区,并将100个代理添加到群集中。 每个代理将是单个分区的领导者,负责仅每秒响应10,000个客户请求。
  3. 使用者负载平衡 :类似于服务器负载平衡,在不同计算机上托管多个使用者可以让您分散使用者负载。 假设您想每秒从100个分区的主题中消耗100万条消息。 您可以创建100个使用者并并行运行它们。 Kafka服务器将为每个使用者分配一个分区,并且每个使用者将并行处理10,000条消息。 由于Kafka仅将每个分区分配给一个使用者,因此在该分区内,每个消息将按顺序使用。

两种分割方式

生产者负责确定消息将进入的分区。 生产者有两个选择来控制此分配:

  • 自定义分区程序 :您可以创建一个实现org.apache.kafka.clients.producer.Partitioner接口的类。 该自定义Partitioner将实现业务逻辑,以决定将消息发送到何处。
  • DefaultPartitioner :如果您不创建自定义分区程序类,则默认情况下将使用org.apache.kafka.clients.producer.internals.DefaultPartitioner类。 默认分区程序足以应付大多数情况,它提供了三个选项:
    1. 手册 :创建ProducerRecord ,请使用重载的构造函数new ProducerRecord(topicName, partitionId,messageKey,message)来指定分区ID。
    2. 散列(对位置敏感) :创建ProducerRecord ,通过调用new ProducerRecord(topicName,messageKey,message)指定messageKeyDefaultPartitioner将使用密钥的哈希值来确保同一密钥的所有消息都发送到同一生产者。 这是最简单,最常见的方法。
    3. Spraying(随机负载平衡) :如果您不想控制要转到哪些分区消息,只需调用new ProducerRecord(topicName, message)即可创建ProducerRecord 。 在这种情况下,分区程序将以循环方式将消息发送到所有分区,以确保平衡的服务器负载。

对Apache Kafka应用程序进行分区

对于第1部分中简单的生产者/消费者示例,我们使用了DefaultPartitioner 。 现在,我们将尝试创建一个自定义分区程序。 对于此示例,假设我们有一个零售站点,供消费者在世界任何地方订购产品。 根据使用情况,我们知道大多数消费者都在美国或印度。 我们希望对我们的应用程序进行分区,以将来自美国或印度的订单发送给他们自己的消费者,而来自其他任何地方的订单将交给第三位消费者。

首先,我们将创建一个实现org.apache.kafka.clients.producer.Partitioner接口的CountryPartitioner 。 我们必须实现以下方法:

  1. 当我们使用配置属性Map初始化Partitioner类时,Kafka将调用configure() 。 此方法初始化特定于应用程序业务逻辑的功能,例如连接到数据库。 在这种情况下,我们需要一个相当通用的分区器,将countryName作为属性。 然后,我们可以使用configProperties.put("partitions.0","USA")将消息流映射到分区。 将来,我们可以使用这种格式来更改哪些国家获得自己的分区。
  2. Producer API为每条消息调用一次partition() 。 在这种情况下,我们将使用它来读取消息并从消息中解析国家/地区的名称。 如果国家/地区名称在countryToPartitionMap ,则它将返回存储在Map partitionId 。 如果不是,它将对国家/地区的值进行哈希处理,并使用它来计算应将其转到哪个分区。
  3. 我们调用close()关闭分区程序。 使用此方法可确保在关闭过程中清除初始化期间获取的所有资源。

请注意,当Kafka调用configure() ,Kafka生产者会将我们为该生产者配置的所有属性传递给Partitioner类。 重要的是,我们仅读取以partitions.开头的那些属性partitions. ,解析它们以获取partitionId ,并将ID存储在countryToPartitionMap

以下是我们对Partitioner接口的自定义实现。

清单1. CountryPartitioner

    public class CountryPartitioner implements Partitioner {
        private static Map<String,Integer> countryToPartitionMap;

        public void configure(Map<String, ?> configs) {
            System.out.println("Inside CountryPartitioner.configure " + configs);
            countryToPartitionMap = new HashMap<String, Integer>();
            for(Map.Entry<String,?> entry: configs.entrySet()){
                if(entry.getKey().startsWith("partitions.")){
                    String keyName = entry.getKey();
                    String value = (String)entry.getValue();
                    System.out.println( keyName.substring(11));
                    int paritionId = Integer.parseInt(keyName.substring(11));
                    countryToPartitionMap.put(value,paritionId);
                }
            }
        }

        public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes,
                             Cluster cluster) {
            List partitions = cluster.availablePartitionsForTopic(topic);
            String valueStr = (String)value;
            String countryName = ((String) value).split(":")[0];
            if(countryToPartitionMap.containsKey(countryName)){
                //If the country is mapped to particular partition return it
                return countryToPartitionMap.get(countryName);
            }else {
                //If no country is mapped to particular partition distribute between remaining partitions
                int noOfPartitions = cluster.topics().size();
                return  value.hashCode()%noOfPartitions + countryToPartitionMap.size() ;
            }
        }

        public void close() {}
    }
    

清单2(下面)中的Producer类与第1部分中的简单生产器非常相似,其中有两个更改用黑体标记:

  1. 我们使用与ProducerConfig.PARTITIONER_CLASS_CONFIG的值相等的键来设置config属性,该值与CountryPartitioner类的完全限定名称匹配。 我们还将countryName设置为partitionId ,从而映射了要传递给CountryPartitioner的属性。
  2. 我们将实现org.apache.kafka.clients.producer.Callback接口的类的实例传递给producer.send()方法的第二个参数。 消息成功发布后,Kafka客户端将调用其onCompletion()方法,并附加一个RecordMetadata对象。 我们将能够使用该对象来查找消息发送到的分区以及分配给已发布消息的偏移量。
清单2.一个分区的生产者

public class Producer {
    private static Scanner in;
    public static void main(String[] argv)throws Exception {
        if (argv.length != 1) {
            System.err.println("Please specify 1 parameters ");
            System.exit(-1);
        }
        String topicName = argv[0];
        in = new Scanner(System.in);
        System.out.println("Enter message(type exit to quit)");

        //Configure the Producer
        Properties configProperties = new Properties();
        configProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
        configProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.ByteArraySerializer");
        configProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

            configProperties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,CountryPartitioner.class.getCanonicalName());
        configProperties.put("partition.1","USA");
        configProperties.put("partition.2","India");
        
        org.apache.kafka.clients.producer.Producer producer = new KafkaProducer(configProperties);
        String line = in.nextLine();
        while(!line.equals("exit")) {
            ProducerRecord<String, String> rec = new ProducerRecord<String, String>(topicName, null, line);
            producer.send(rec, new Callback() {
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    System.out.println("Message sent to topic ->" + metadata.topic()+ " ,parition->" + metadata.partition() +" stored at offset->" + metadata.offset());
;
                }
            } );
            line = in.nextLine();
        }
        in.close();
        producer.close();
    }
}

为使用者分配分区

Kafka服务器保证仅将一个分区分配给一个使用者,从而保证消息使用的顺序。 您可以手动分配分区,也可以自动分配分区。

如果您的业务逻辑需要更多控制权,那么您将需要手动分配分区。 在这种情况下,您可以使用KafkaConsumer.assign(<listOfPartitions>)将每个使用者感兴趣的分区列表传递给Kakfa服务器。

自动分配分区是默认的也是最常见的选择。 在这种情况下,Kafka服务器将为每个使用者分配一个分区,并将重新分配分区以扩展新使用者。

假设您要创建一个具有三个分区的新主题。 当您为新主题启动第一个使用者时,Kafka会将所有三个分区分配给同一使用者。 如果然后启动第二个使用者,则Kafka将重新分配所有分区,将一个分区分配给第一个使用者,其余两个分区分配给第二个使用者。 如果添加第三个使用者,Kafka将再次重新分配分区,以便为每个使用者分配一个分区。 最后,如果您启动第四个和第五个使用者,那么三个使用者将具有分配的分区,但是其他使用者将不会收到任何消息。 如果最初的三个分区之一出现故障,Kafka将使用相同的分区逻辑将该使用者的分区重新分配给其他使用者之一。

翻译自: https://www.infoworld.com/article/3066873/big-data-messaging-with-kafka-part-2.html

apache kafka

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值