記事ディレクトリ
概要
実際のアプリケーションでは、多くの場合、ビジネス ニーズに応じて Kafka コンシューマーの監視を動的に有効/無効にすることが必要になります。たとえば、特定の期間内でトピックの消費を一時停止したり、特定の条件下でのみトピックの消費を開始したりする必要がある場合があります。
Spring Bootでは、消費の動的な制御や終了、モニタリングの動的な開閉を実現するために、Spring Kafkaが提供する一部の機能を利用することができる。
一連の考え
まず、Kafka コンシューマーの関連プロパティを構成する必要があります。Spring Boot では、対応する構成を application.properties または application.yml ファイルに追加することでこれを実現できます。
構成例を次に示します。
spring.kafka.consumer.bootstrap-servers=<Kafka服务器地址>
spring.kafka.consumer.group-id=<消费者组ID>
次に、Kafka コンシューマーを作成し、@KafkaListener
アノテーションを使用してリッスンする Kafka トピックを指定し、対応するメッセージ処理メソッドを記述します。例えば:
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class KafkaConsumer {
@KafkaListener(topics = "<Kafka主题>")
public void receive(String message) {
// 处理接收到的消息
}
}
次の 2 つの方法を使用して、消費を制御または終了し、モニタリングを動的に開始または終了できるようになりました。
方法 1:@KafkaListener
注釈autoStartup
属性を使用する
@KafkaListener
アノテーションにautoStartup
は という名前の属性があり、コンシューマを自動的に開始するかどうかを制御するために使用できます。デフォルトでは、その値は でありtrue
、自動起動を意味します。に設定されている場合false
、コンシューマは自動的に起動しません。
@KafkaListener(topics = "<Kafka主题>", autoStartup = "false")
public void receive(String message) {
// 处理接收到的消息
}
実行時にコンシューマーを動的に開始するには、KafkaListenerEndpointRegistry
Bean を介して手動でコンシューマーを開始します。
@Autowired
private KafkaListenerEndpointRegistry endpointRegistry;
// 启动消费者
endpointRegistry.getListenerContainer("<KafkaListener的bean名称>").start();
stop()
同様に、次のメソッドを使用してコンシューマを停止することもできます。
// 停止消费者
endpointRegistry.getListenerContainer("<KafkaListener的bean名称>").stop();
方法 2: KafkaListenerEndpointRegistry
Bean のpause()
sumresume()
メソッドを使用する
KafkaListenerEndpointRegistry
この Bean は、消費者のリスニングを一時停止および再開するためのメソッドpause()
を提供しますresume()
。
@Autowired
private KafkaListenerEndpointRegistry endpointRegistry;
// 暂停消费者监听
endpointRegistry.getListenerContainer("<KafkaListener的bean名称>").pause();
// 恢复消费者监听
endpointRegistry.getListenerContainer("<KafkaListener的bean名称>").resume();
これらのメソッドを使用すると、実行時に消費を動的に制御またはクローズしたり、リスニングを動的にオンまたはオフにしたりできます。
コード
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.listener.ConsumerAwareListenerErrorHandler;
import org.springframework.kafka.listener.ContainerProperties;
import java.util.HashMap;
import java.util.Map;
/**
* @author artisan
*/
@Slf4j
@Configuration
public class KafkaConfig {
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServer;
@Value("${spring.kafka.consumer.auto-offset-reset}")
private String autoOffsetReset;
@Value("${spring.kafka.consumer.enable-auto-commit}")
private String enableAutoCommit;
@Value("${spring.kafka.consumer.key-deserializer}")
private String keyDeserializer;
@Value("${spring.kafka.consumer.value-deserializer}")
private String valueDeserializer;
@Value("${spring.kafka.consumer.group-id}")
private String group_id;
@Value("${spring.kafka.consumer.max-poll-records}")
private String maxPollRecords;
@Value("${spring.kafka.consumer.max-poll-interval-ms}")
private String maxPollIntervalMs;
@Value("${spring.kafka.listener.concurrency}")
private Integer concurrency;
private final String consumerInterceptor = "net.zf.module.system.kafka.interceptor.FailureRateInterceptor";
/**
* 消费者配置信息
*/
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>(32);
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,bootstrapServer);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,enableAutoCommit);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,keyDeserializer);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,valueDeserializer);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG,maxPollRecords);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG,maxPollIntervalMs);
props.put(ConsumerConfig.GROUP_ID_CONFIG,group_id);
props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG,consumerInterceptor );
return props;
}
/**
* 消费者批量工厂
*/
@Bean
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> batchFactory() {
ConcurrentKafkaListenerContainerFactory<String,String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerConfigs()));
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
factory.setBatchListener(true);
factory.setConcurrency(concurrency);
return factory;
}
/**
* 异常处理器
*
* @return
*/
@Bean
public ConsumerAwareListenerErrorHandler consumerAwareListenerErrorHandler() {
return (message, exception, consumer) -> {
// log.error("消息{} , 异常原因{}", message, exception.getMessage());
log.error("consumerAwareListenerErrorHandler called");
return null;
};
}
}
使用
@KafkaListener(topicPattern = KafkaTopicConstant.ATTACK_MESSAGE + ".*",
containerFactory = "batchFactory",
errorHandler = "consumerAwareListenerErrorHandler",
id = "attackConsumer")
public void processMessage(List<String> records, Acknowledgment ack) {
log.info("AttackKafkaConsumer 当前线程 {} , 本次拉取的数据总量:{} ", Thread.currentThread().getId(), records.size());
try {
List<AttackMessage> attackMessages = new ArrayList();
records.stream().forEach(record -> {
messageExecutorFactory.process(KafkaTopicConstant.ATTACK_MESSAGE).execute(record, attackMessages);
});
if (!attackMessages.isEmpty()) {
attackMessageESService.addDocuments(attackMessages, false);
}
} finally {
ack.acknowledge();
}
}
このコードでは、 @KafkaListener アノテーションは、これが Kafka コンシューマーであることを示します。
- topicPattern パラメーターは、コンシューマーが聞きたいトピックのパターン、つまり
KafkaTopicConstant.ATTACK_MESSAGE
で始まるすべてのトピックを指定します。 - ContainerFactory パラメーターは、Kafka リスナー コンテナーの作成に使用されるファクトリ クラス名を指定します。
- errorHandler パラメーターは、リスナーによってスローされた例外を処理するために使用されるエラー ハンドラーを指定します。id パラメータはコンシューマの ID を指定します。
コンシューマのメソッドでは、メッセージが到着すると、 records パラメータにはメッセージ レコードのセットが含まれ、ack パラメータはこれらのメッセージが消費されたことを手動で確認するために使用されます。
このメソッドでは、最初に現在のスレッド ID とプルされたデータの総量が記録されます。メッセージ レコードを 1 つずつ処理し、処理結果を AttackMessages という名前のリストに保存します。リストが空でない場合は、ES 検索エンジンに追加されます。
最後に、メッセージが消費されたことを手動で確認します。
【コントロール】
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class KafkaConsumerController {
@Autowired
private KafkaListenerEndpointRegistry registry;
/**
* 开启监听
*/
@GetMapping("/start")
public void start() {
// 判断监听容器是否启动,未启动则将其启动
if (!registry.getListenerContainer("attackConsumer").isRunning()) {
log.info("start ");
registry.getListenerContainer("attackConsumer").start();
}
// 将其恢复
registry.getListenerContainer("attackConsumer").resume();
log.info("resume over ");
}
/**
* 关闭监听
*/
@GetMapping("/pause")
public void pause() {
// 暂停监听
registry.getListenerContainer("attackConsumer").pause();
log.info("pause");
}
}
拡大
KafkaListenerエンドポイントレジストリ
KafkaListenerEndpointRegistry
これは、Kafka コンシューマー リスナーの登録と起動を管理するために Spring Kafka によって提供されるコンポーネントです。これは、リスナー コンテナの登録と開始、リスナー コンテナの一時停止と再開など、Kafka リスナー コンテナを管理するためのメソッドを提供するインターフェイスです。
Spring Boot アプリケーションでアノテーションを使用すると@KafkaListener
、Spring Kafka は自動的に のKafkaListenerEndpointRegistry
インスタンスを作成し、それを使用してすべての Kafka リスナー コンテナーを管理します。これは Spring Kafka のコアコンポーネントであり、Kafka コンシューマーの監視と制御に使用されます。