8.消费者客户端开发

消费者客户端开发

一个正常的消费逻辑需要具备以下几个步骤:
1.配置消费者客户端参数及创建相应的消费者实例。
2.订阅主题
3.拉取消息并消费
4.提交消费位移
5.关闭消费者实例
默认配置是从订阅开始后,才开始消费新消息如果需要从起始位置消费那么需要修改消费者客户端参数(可见12节)

//代码清单8-1 消费者客户端示例
public class KafkaConsumerAnalysis {

    public static final String brokerList = "172.16.15.89:9092";
    public static final String topic = "topic-demo";
    public static final String groupId = "group.demo";
    public static final AtomicBoolean isRunning = new AtomicBoolean(true);

    public static Properties ininConfig(){
        Properties properties=new Properties();
        properties.put("key.deserializer", StringDeserializer.class.getName());
        properties.put("value.deserializer",StringDeserializer.class.getName());
        properties.put("bootstrap.servers",brokerList);
        properties.put("group.id",groupId);
        properties.put("client.id","consumer.client.id.demo");
        return properties;
    }

    public static void main(String[] args) {
        Properties properties=ininConfig();
        KafkaConsumer<String,String> consumer=new KafkaConsumer<String, String>(properties);
        consumer.subscribe(Arrays.asList(topic));

        try {
            while (isRunning.get()) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.println("topic = " + record.topic()
                            + ", partition = " + record.partition()
                            + ", offset = " + record.offset());

                    System.out.println("key = " + record.key()
                            + ", value = " + record.value());
                }
            }
        }  catch (Exception e) {
            e.printStackTrace();
        } finally {
            consumer.close();
        }

    }

}

必要的参数配置

在创建真正的消费者实例之前需要做参数配置,比如上节设置消费组名称、连接地址等,参考代码清单8-1中的 initConfig() 方法,在 Kafka 消费者客户端 KafkaConsumer 中有4个参数是必填的。

  • bootstrap.servers:该参数的释义和生产者客户端 KafkaProducer 中的相同,用来指定连接 Kafka 集群所需的 broker 地址清单,具体内容形式为 host1:port1,host2:post,可以设置一个或多个地址,中间用逗号隔开,此参数的默认值为“”。注意这里并非需要设置集群中全部的 broker 地址,消费者会从现有的配置中查找到全部的 Kafka 集群成员。这里设置两个以上的 broker 地址信息,当其中任意一个宕机时,消费者仍然可以连接到 Kafka 集群上。
  • key.deserializer 和 value.deserializer:与生产者客户端 KafkaProducer 中的 key.serializer和value.serializer 参数对应。消费者从 broker 端获取的消息格式都是字节数组(byte[])类型,所以需要执行相应的反序列化操作才能还原成原有的对象格式。这两个参数分别用来指定消息中 key 和 value 所需反序列化操作的反序列化器,这两个参数无默认值。注意这里必须填写反序列化器类的全限定名,比如示例中的 org.apache.kafka.common.serialization.StringDeserializer,单单指定 StringDeserializer 是错误的。有关更多的反序列化内容可以参考下一节。
  • group.id:消费者隶属的消费组的名称,默认为“”。如果设置为空 会报异常 ERROR org.apache.kafka.clients.consumer.internals.AbstractCoordinator - Attempt to join group failed due to fatal error: The configured groupId is invalid。一般情况下,这个参数需要设置成具有一定的业务意义的名称。

注意到代码清单8-1中的 initConfig() 方法里还设置了一个参数 client.id,这个参数用来设定 KafkaConsumer 对应的客户端id,默认值也为“”。如果客户端不设置,则 KafkaConsumer 会自动生成一个非空字符串,内容形式如“consumer-1”、“consumer-2”,即字符串“consumer-”与数字的拼接。

就好比生产者客户端参数中有 ProducerConfin 类一样,为了方便书写以及减少错误,我们可以直接使用客户端中的org.apache.kafka.clients.consumer.ConsumerConfig 类来做一定程度上的预防。而序列化名称也可以通过Java中的技巧做进一步改进:

public static Properties ininConfig(){
        Properties properties=new Properties();
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,brokerList);
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,groupId);
        properties.put(ConsumerConfig.CLIENT_ID_CONFIG,"consumer.client.id.demo");
        return properties;
    }

订阅主题和分区

创建好消费者后,需要给该消费者订阅相应的主题。一个消费者可以订阅一个或多个主题,代码清单8-1中使用 subscribe() 方法订阅了一个主题,对于这个方法而言,既可以以集合形式订阅多个主题也可以以正则表达式订阅特定模式的主题。该方法的几个重载方法如下:

	void subscribe(Collection<String> var1);

    void subscribe(Collection<String> var1, ConsumerRebalanceListener var2);
    
    void subscribe(Pattern var1);

    void subscribe(Pattern var1, ConsumerRebalanceListener var2);

对于消费者使用集合的方式(subscribe(Collection))来订阅主题而言,比较容易理解,订阅了什么主题就消费集合中的主题。如果两次订阅的主题不一样,那么以最后一次为准:

consumer.subscribe(Arrays.asList(topic1));
consumer.subscribe(Arrays.asList(topic2));

如果消费者采用正则表达式的方式(subscribe(Pattern))订阅,在之后的过程中,如果创建了新的主题,且主题名称与正则表达式相匹配,那么该消费者就可以消费新主题的消息。如果应用程序需要消费多个主题,并且可以处理不同类型,那么这种订阅方式就很有效。在Kafka和其他系统之间进行数据复制时,这种表达式的方式就显得很常见。正则表达式的实例如下:

consumer.subscribe(Pattern.compile("topic-.*"));

在subscribe的重载方法中,有一个参数是 ConsumerRebalanceListener,这个是用来设置相应的再均衡监听器的。

消费者不仅可以通过 KafkaConsumer.subscribe() 方法订阅主题,还可以指定订阅某些主题的特定分区。在 KafkaConsumer 中还提供了一个 assign() 方法实现这些功能:

public void assign(Collection<TopicPartition> partitions)

这个方法只接受一个参数 partitions,用来指定订阅的分区集合,在 Kafka 客户端中 TopicPartition 类用来表示分区。这个类可以和我们通常说的主题–分区的概念映射起来。

public final class TopicPartition implements Serializable {
	    private int hash = 0;
	    // 自身的分区编号
	    private final int partition;
	    // 分区所属的主题
	    private final String topic;
	
	    public TopicPartition(String topic, int partition) {
	        this.partition = partition;
	        this.topic = topic;
	    }
	
	    public int partition() {
	        return this.partition;
	    }
	
	    public String topic() {
	        return this.topic;
	    }
	       //省略hashCode()、equals()和toString()方法
    }

将代码清单8-1中的 subscribe() 方法修改为 assign() 方法,这里只订阅 topic-demo 主题中分区编号为0的分区,相关代码如下:

扫描二维码关注公众号,回复: 10922152 查看本文章
 consumer.assign(Arrays.asList(new TopicPartition(topic,0)));

如果事先不知道主题有多少个分区怎么办?KafkaConsumer 中的 partitionsFor() 方法可以用来查询指定主题的元数据信息:

List<PartitionInfo> partitionsFor(String var1);

PartitionInfo 即为主题的分区元数据信息,主要结构如下:

public class PartitionInfo {
	// 主题名称
    private final String topic;
    // 分区编号
    private final int partition;
    // 分区的leader副本所在位置
    private final Node leader;
    // 分区的AR集合
    private final Node[] replicas;
    // 分区的ISR集合
    private final Node[] inSyncReplicas;
    // 分区的OSR集合
    private final Node[] offlineReplicas;
     //这里省略了构造函数、属性提取、toString等方法
 }

通过 partitionFor() 方法的协助,可以通过 assign() 方法来实现订阅主题(全部分区)的功能,示例如下:

		List<TopicPartition> partitions=new ArrayList<>();
        List<PartitionInfo> partitionInfos=consumer.partitionsFor(topic);
        if (partitionInfos!=null){
            for (PartitionInfo partitionInfo:partitionInfos){
                partitions.add(new TopicPartition(partitionInfo.topic(),partitionInfo.partition()));
            }
        }
        consumer.assign(partitions);

取消订阅
既然有订阅,那么就有取消订阅,可以使用 KafkaConsumer 中的 unsubscribe() 方法取消主题的订阅。这个方法可以取消通过 subscribe(Collection)、subscribe(Pattern) 和 assign(Collection) 三个方法实现的订阅。示例代码如下:

 consumer.unsubscribe();

如果将 subscribe(Collection) 或 assign(Collection) 中的集合参数设置为空集合,那么作用等同于 unsubscribe() 方法
如果没用订阅任何主题或分区,继续执行消费程序的时候会报出 IllegalStateException 异常:
在这里插入图片描述
使用 subscribe() 方法来订阅主题具有消费者自动再均衡的功能,在多个消费者的情况下可以根据分区分配策略来自动分配各个消费者和分区的关系。当消费组内的消费者增加或者减少时(或主题的分区增加减少时),分区的分配关系会自动调整,以实现负载均衡和故障转移。而通过 assign() 订阅分区时,是不具备消费者自动均衡的功能的。其实从参数就可以看出两种 subscribe() 方法都有 ConsumerRebalanceListener 类型参数,而 assign() 方法没有。

发布了76 篇原创文章 · 获赞 1 · 访问量 5099

猜你喜欢

转载自blog.csdn.net/qq_38083545/article/details/94180793