kafka单排日志—springboot整合kafka遗留问题探究

468 阅读3分钟

前言

前文中,已经完成了springboot对kafka的整合,还有一些问题需要解决的就继续记录下。

定制个性化消费者分析

如下,咱们写在配置文件中的消费者的各项配置是针对所有消费者的,那么当咱们想定制个性化消费者的时候,当然就不想用yml中消费者的通用配置了。

image.png

怎么解决这个问题呢?大家还记得咱们自己写过的KafkaListenerContainerFactory嘛,当时咱们配置了两个,一个主要是为了批量消费、另一个是为了单条数据消费。这个

image.png

KafkaListenerContainerFactory就可以规定个性化的消费者参数,可以对这个类进行扩展达到咱们的目的,怎么玩呢。 其实呀,这还要取决于ConsumerFactory,这个类真正传递过来的实例是DefaultKafkaConsumerFactory

image.png

而DefaultKafkaConsumerFactory中有一个属性configs,这个就用来装载消费者的各种配置。

image.png DefaultKafkaConsumerFactory的一个构造方法是,直接传递一个map,那么咱们不用默认的ConsumerFactory,直接自己制造一个DefaultKafkaConsumerFactory来用。看看能否达到目的:

image.png

定制个性化消费者实践

创建消费者的各个配置项: image.png

private Map<String, Object> consumerConfig1() {
    Map<String, Object> propsMap = new HashMap<>(32);
    propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
    propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "102");
    propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, "group-1");
    propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
    propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "20");
    propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"XXXX:9092,xxxx:9092, xxxx:9092");
    return propsMap;
}

创建监听工厂:

@Bean("customized1")
@NotNull
public KafkaListenerContainerFactory<?> customizedFactory1() {
    ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
    // 自定义DefaultKafkaConsumerFactory
    DefaultKafkaConsumerFactory<Integer, String> defaultKafkaConsumerFactory = new DefaultKafkaConsumerFactory<Integer, String>(consumerConfig1());
    factory.setConsumerFactory(defaultKafkaConsumerFactory);
    factory.setConcurrency(5);
    factory.getContainerProperties().setPollTimeout(1500);
    // 批量拉取数据
    factory.setBatchListener(true);
    // 设置每个@KafkaListener的线程数
    factory.setConcurrency(5);
    // 设置手动提交ack,对于要求较为严格的业务较为合适
    factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
    return factory;
}

创建topic:


private static final String TOPIC_THREE = "customizedTopic";

@Bean
public NewTopic initialTopic2() {
    log.info("create new topic now");
    return TopicBuilder.name(TOPIC_THREE).partitions(6).replicas(2).build();
}

创建监听器:

/**
 * @param recordList
 * @param acknowledgment 使用自定义的消费者
 */
@KafkaListener(id = "consumer4", topics = "customizedTopic", containerFactory = "customized1",
        properties = {
                ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG + "=50000" // 如果超过5分钟pull不到数据,那么尝试自行拉取
        }
)
public void onMessage4(List<ConsumerRecord<?, ?>> recordList, Acknowledgment acknowledgment) {
    logger.info(">>>批量消费一次,records.size()=" + recordList.size());
    logger.info("=================================================start");
    try {
        for (ConsumerRecord<?, ?> record : recordList) {
            logger.info("get message from record:{}", record.value());

        }
        acknowledgment.acknowledge();
        logger.info("=================================================end");
    } catch (Exception e) {
        logger.info("exception occur when consume message:{}", e.getMessage());
        acknowledgment.acknowledge();
    }

}

重启下项目,配置已经生效:

image.png 接下来可以模仿之前的操作,去正常收发消息了。

image.png 消费是正常的。

同组不同消费者消费同一topic

为了加快消费速度,可以为一个topic构建多个消费者进行消费。咱们用同组的不同消费者来测试下:

/**
 * @param recordList
 * @param acknowledgment KafkaListener id :消费者线程的命名规则.
 *                       groupId:指定该消费组的消费组名
 *                       topics:指定要监听哪些topic 这个是从所有的分区取数据吗?
 */
@KafkaListener(id = "consumer2", groupId = "felix-group", topics = "testtopic1", containerFactory = "batch",
        properties = {
                ConsumerConfig.MAX_POLL_RECORDS_CONFIG + "=10",// 每次最多取10个进行消费
                ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG + "=50000" // 如果超过5分钟pull不到数据,那么尝试自行拉取
        }
)
public void onMessage3(List<ConsumerRecord<?, ?>> recordList, Acknowledgment acknowledgment) {
    logger.info(">>>批量消费一次,records.size()=" + recordList.size());
    logger.info("===================================================start");
    try {
        for (ConsumerRecord<?, ?> record : recordList) {
            Object value = record.value();
            logger.info("get message from record:{}", value);
            copyOnWriteArrayList1.add(record.value().toString());
        }
        acknowledgment.acknowledge();
        logger.info("====================================================end");
    } catch (Exception e) {
        logger.info("exception occur when consume message:{}", e.getMessage());
        acknowledgment.acknowledge();
    }

}

/**
 * @param recordList
 * @param acknowledgment 同一组内的不同消费者的消费情况咱们来观察一下 针对的是testtopic1,两个消费者都属于felix-group
 *                       这里我想说的是,为了保证数据可以不重复消费,咱们最好还是对消息做幂等的操作吧
 */
@KafkaListener(id = "consumer23", groupId = "felix-group", topics = "testtopic1", containerFactory = "batch",
        properties = {
                ConsumerConfig.MAX_POLL_RECORDS_CONFIG + "=10",// 每次最多取10个进行消费
                ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG + "=50000" // 如果超过5分钟pull不到数据,那么尝试自行拉取
        }
)
public void onMessage(List<ConsumerRecord<?, ?>> recordList, Acknowledgment acknowledgment) {
    logger.info(">>>批量消费一次,同一组内的第二个消费者!records.size()=" + recordList.size());
    logger.info("=================================================start");
    try {
        for (ConsumerRecord<?, ?> record : recordList) {
            Object value = record.value();
            logger.info("get message from record:{}", record.value());
            copyOnWriteArrayList2.add(value.toString());
        }
        acknowledgment.acknowledge();
        logger.info("==================================================end");
    } catch (Exception e) {
        logger.info("exception occur when consume message:{}", e.getMessage());
        acknowledgment.acknowledge();
    }

}

来简单测试下:

image.png

可以的 ,没什么问题。不同组的不同消费者对同一topic的消费还存在些问题。

本篇暂时到此。