簡體   English   中英

Spring kafka生產者線程不斷增加

[英]Spring kafka producer threads keep increasing

我們正在使用帶有 Spring 的 Kafka,我們目前正在對應用程序進行一些負載測試。 在開始負載測試的幾分鍾內,Tomcat 停止響應,在分析線程轉儲時,我看到相當多的 Kafka 生產者線程,並假設這可能是應用程序掛起的原因。線程數相當高,即在幾分鍾就有 200 多個 Kafka 生產者線程。 有什么辦法可以關閉這些生產者線程。 下面給出的是我的 Spring Kafka 生產者配置。

編輯:在我們的應用程序中,我們有一個事件發布/訂閱,我正在使用 Kafka 發布事件。 分區數:15,並發:5

@Bean
public ProducerFactory<String, Object> producerFactory() {
    Map<String, Object> configProps = new HashMap<>();
    configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
    configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
    configProps.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, KafkaCustomPartitioner.class);
    configProps.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
    configProps.put(ProducerConfig.LINGER_MS_CONFIG, 200);


    DefaultKafkaProducerFactory factory = new DefaultKafkaProducerFactory<>(configProps);
    factory.setTransactionIdPrefix(serverId+"-tx-");
    // factory.setProducerPerConsumerPartition(false);
    return factory;
}

public ConsumerFactory<String, Object> consumerFactory(String groupId) {
    Map<String, Object> props = new HashMap<>();
    props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
    props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
    props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
    props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
    props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG,"read_committed");
    props.put(ConsumerConfig.GROUP_ID_CONFIG,"custom-group-id");
    props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG,60000);
    props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG,5000);
    props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG,20);
    props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,600000);
    props.put(JsonDeserializer.TRUSTED_PACKAGES, "org.xxx.xxx.xxx");
    return new DefaultKafkaConsumerFactory<>(props);
}


@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> customKafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
    //factory.setConcurrency(eventTopicConcurrency);
    factory.getContainerProperties().setAckOnError(false);
    factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE);
    factory.setErrorHandler(new SeekToCurrentErrorHandler());
    factory.setConsumerFactory(consumerFactory("custom-group-id"));

    return factory;
}

以下是我的發布者和訂閱者代碼

@Override
public void publish(Event event) {
    //try {
        DomainEvent event = event.getDomainEvent();
        ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topicName,
                event.getMainDocumentId() != null ? event.getMainDocumentId() : null, event);

        future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {

            @Override
            public void onSuccess(SendResult<String, Object> result) {
                if(LOGGER.isDebugEnabled())
                    LOGGER.debug("Published event {} : {}",event.getEventName(), event.getEventId());
            }

            @Override
            public void onFailure(Throwable ex) {
                LOGGER.error("Failed to publish event {} : {} ", event.getEventName(), event.getEventId());
                throw new RuntimeException(ex);
            }
        });
    }

偵聽器:我們有多個事件訂閱者,因此當我們從 Kafka 接收到一個事件時,我們會為每個訂閱者生成新線程來處理該事件,並且當所有訂閱者都完成處理時,我們會提交偏移量。

@KafkaListener(topics = "${kafka.event.topic.name}-#{ClusterConfigSplitter.toClusterId('${cluster.info}')}", concurrency="${kafka.event.topic.concurrency}", clientIdPrefix="${web.server.id}-event-consumer", containerFactory = "customKafkaListenerContainerFactory")
public void eventTopicListener(Event event, Acknowledgment ack)
        throws InterruptedException, ClassNotFoundException, IOException {

    if(LOGGER.isDebugEnabled())
        LOGGER.debug("Received event {} : {}", event.getDomainEvent().getEventName(), event.getDomainEvent().getEventId());

    DomainEvent domainEvent = event.getDomainEvent();

    List<EventSubscriber> subcribers = new ArrayList<>();
    for (String failedSubscriber : event.getSubscribersToRetry()) {
        subcribers.add(eventSubcribers.get(failedSubscriber));
    }

    CountDownLatch connectionLatch = new CountDownLatch(subcribers.size());

    List<String> failedSubscribers = new ArrayList<>();

    for (EventSubscriber subscriber : subcribers) {

        taskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                tenantContext.setTenant(domainEvent.getTenantId());
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                def.setName(domainEvent.getEventId() + "-" + subscriber.getClass().getName());
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

                TransactionStatus status = txManager.getTransaction(def);

                try {
                    subscriber.handle(domainEvent);
                    txManager.commit(status);
                } catch (Exception ex) {
                    LOGGER.error("Processing event {} : {} failed for {} - {}", domainEvent.getEventName(), domainEvent.getEventId(), ex);

                    txManager.rollback(status);
                    failedSubscribers.add(subscriber.getClass().getName());
                }

                connectionLatch.countDown();

                if(LOGGER.isDebugEnabled())
                    LOGGER.debug("Processed event {} : {} by {} ", domainEvent.getEventName(), domainEvent.getEventId(), subscriber.getClass().getName());
            }
        });

    }

    connectionLatch.await();

    ack.acknowledge();

    if(failedSubscribers.size()>0) {

        eventPersistenceService.eventFailed(domainEvent, failedSubscribers, event.getRetryCount()+1);

    }




}

事務管理器

    @Bean
@Primary
public PlatformTransactionManager transactionManager(EntityManagerFactory factory,@Qualifier("common-factory") EntityManagerFactory commonFactory, ProducerFactory producerFactory){

    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(factory);

    JpaTransactionManager commonTransactionManager = new JpaTransactionManager();
    commonTransactionManager.setEntityManagerFactory(commonFactory);

    KafkaTransactionManager kafkaTransactionManager= new KafkaTransactionManager(producerFactory);

    return new ChainedKafkaTransactionManager(kafkaTransactionManager,commonTransactionManager,transactionManager);

}

我將寫一個更完整的答案來幫助其他可能找到這個問題的人。

使用事務時,默認情況下,我們必須為每個group/topic/partition組合創建一個新的生產者(假設事務由消費者線程啟動); 這是為了在發生再平衡時可以適當地隔離生產者。

2.5 kafka-clients 有一個改進的算法來改善這種情況,我們不再需要所有這些生產者。

但是,代理必須升級到 2.5.0 才能使用此功能。

即將發布的 2.5.0.RELEASE(明天到期)允許將這個新線程 model 用於事務生產者。

發布候選版可用於測試。

有關新功能的文檔在此處

但是,您已禁用創建提供適當生產者防護的生產者。

factory.setProducerPerConsumerPartition(false);

所以在這種情況下,你應該看到生產者被緩存了; 擁有這么多生產者是不尋常的,除非您的偵聽器容器具有巨大的並發性並且生產量非常大。

生產者工廠目前不支持限制緩存大小。

也許您可以編輯您的問題以解釋更多關於您的應用程序正在做什么並顯示更多代碼/配置。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM