Java Spring Cloud Stream可轮询消费者dlq和errorChannel don';如果使用不同的线程,则无法工作

Java Spring Cloud Stream可轮询消费者dlq和errorChannel don';如果使用不同的线程,则无法工作,java,spring-kafka,spring-cloud-stream,spring-cloud-stream-binder-kafka,Java,Spring Kafka,Spring Cloud Stream,Spring Cloud Stream Binder Kafka,为了使用带有Kafka binder的Spring Cloud Stream 3.1.1管理长时间运行的任务,我们需要使用可轮询的使用者在单独的线程中手动管理消耗,以便Kafka不会触发重新平衡。为此,我们定义了一个新的注释来管理可轮询消费者。这种方法的问题是,工作需要在单独的线程中进行管理。抛出的任何异常最终都不会出现在errorChannel和DLQ中 private final ExecutorService executor = Executors.newFixedThreadPoo

为了使用带有Kafka binder的Spring Cloud Stream 3.1.1管理长时间运行的任务,我们需要使用可轮询的使用者在单独的线程中手动管理消耗,以便Kafka不会触发重新平衡。为此,我们定义了一个新的注释来管理可轮询消费者。这种方法的问题是,工作需要在单独的线程中进行管理。抛出的任何异常最终都不会出现在
errorChannel
和DLQ中

  private final ExecutorService executor = Executors.newFixedThreadPool(1);

  private volatile boolean paused = false;

  @Around(value = "@annotation(pollableConsumer) && args(dataCapsule,..)")
  public void handleMessage(ProceedingJoinPoint joinPoint,
      PollableConsumer pollableConsumer, Object dataCapsule) {
    if (dataCapsule instanceof Message) {
      Message<?> message = (Message<?>) dataCapsule;
      AcknowledgmentCallback callback = StaticMessageHeaderAccessor
          .getAcknowledgmentCallback(message);
      callback.noAutoAck();

      if (!paused) {
        // The separate thread is not busy with a previous message, so process this message:
        Runnable runnable = () -> {
          try {
            paused = true;

            // Call method to process this Kafka message
            joinPoint.proceed();

            callback.acknowledge(Status.ACCEPT);
          } catch (Throwable e) {
            callback.acknowledge(Status.REJECT);
            throw new PollableConsumerException(e);
          } finally {
            paused = false;
          }
        };

        executor.submit(runnable);
      } else {  

        // The separate thread is busy with a previous message, so re-queue this message for later:
        callback.acknowledge(Status.REQUEUE);
      }
    }
  }
private final executor service executor=Executors.newFixedThreadPool(1);
private=false;
@大约(value=“@annotation(pollableConsumer)和&args(dataCapsule,…”)
公共无效handleMessage(处理连接点连接点,
PollableConsumer PollableConsumer,对象数据胶囊){
if(信息的数据胶囊实例){
消息消息=(消息)数据胶囊;
AcknowledgementCallback=StaticMessageHeaderAccessor
.GetAcknowledgementCallback(消息);
callback.noAutoAck();
如果(!暂停){
//单独的线程没有处理上一条消息,因此处理此消息:
Runnable Runnable=()->{
试一试{
暂停=真;
//调用方法来处理此Kafka消息
joinPoint.procedure();
回调。确认(状态。接受);
}捕获(可丢弃的e){
回调.确认(状态.拒绝);
抛出新的PollableConsumerException(e);
}最后{
暂停=错误;
}
};
执行人提交(可运行);
}否则{
//单独的线程正忙于处理前一条消息,因此请将此消息重新排队,以便以后使用:
回拨.确认(状态.重新请求);
}
}
}
我们可以创建一个不同的输出通道来在异常情况下发布消息,但它感觉我们正在尝试实现一些可能不必要的东西

更新1

我们添加了以下bean:

  @Bean
  public KafkaTemplate<String, byte[]> kafkaTemplate() {
    return new KafkaTemplate<>(producerFactory());
  }
  @Bean
  public ProducerFactory<String, byte[]> producerFactory() {
    Map<String, Object> configProps = new HashMap<>();
    configProps.put(
        org.apache.kafka.clients.producer.ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
        "http://localhost:9092");
    configProps.put(
        org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
        StringSerializer.class);
    configProps.put(
        org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
        KafkaAvroSerializer.class);
    return new DefaultKafkaProducerFactory<>(configProps);
  }
  @Bean
  public KafkaAdmin admin() {
    Map<String, Object> configs = new HashMap<>();
    configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "http://localhost:9092");
    return new KafkaAdmin(configs);
  }
  @Bean
  public NewTopic topicErr() {
    return TopicBuilder.name("ERR").partitions(1).replicas(1).build();
  }
  @Bean
  public SeekToCurrentErrorHandler eh(KafkaOperations<String, byte[]> template) {
    return new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(
        template,
        (cr, e) -> new TopicPartition("ERR", 1)),
        new FixedBackOff(0L, 1L));
  }
@Bean
公共卡夫卡模板卡夫卡模板(){
返回新的卡夫卡模板(producerFactory());
}
@豆子
公共生产工厂生产工厂(){
Map configProps=new HashMap();
配置props.put(
org.apache.kafka.clients.producer.ProducerConfig.BOOTSTRAP\u SERVERS\u CONFIG,
"http://localhost:9092");
配置props.put(
org.apache.kafka.clients.producer.ProducerConfig.KEY\u SERIALIZER\u CLASS\u CONFIG,
StringSerializer.class);
配置props.put(
org.apache.kafka.clients.producer.ProducerConfig.VALUE\u SERIALIZER\u CLASS\u CONFIG,
卡夫卡夫罗塞里泽(KafkaAvroSerializer.class);
返回新的DefaultKafkaProducerFactory(configProps);
}
@豆子
公共卡夫卡行政管理(){
Map configs=new HashMap();
configs.put(AdminClientConfig.BOOTSTRAP\u SERVERS\u CONFIG,“http://localhost:9092");
返回新的卡夫卡管理员(配置);
}
@豆子
公共新拓扑{
返回TopicBuilder.name(“ERR”).partitions(1).replications(1.build();
}
@豆子
public SeektocurInterrorHandler eh(卡夫卡操作模板){
返回新的SEEKTocurInterrorHandler(新的死信发布恢复程序(
模板,
(cr,e)->新的主题划分(“ERR”,1)),
新固定回退(0L、1L);
}
spring.cloud.stream.kafka.bindings.channel name.consumer中未设置
enable dlq
但是我们仍然看不到任何针对ERR主题的消息。 即使是主线程抛出的任何异常

如果
enable dlq
设置为true,则主线程上的异常将发布到默认dlq主题中,并且正如预期的那样,子线程上的异常将被忽略

更新2

加里的例子似乎在总体上起作用。尽管在使用不推荐使用的StreamListner方法而不是函数时,我们需要做一些修改,但仍有一些问题我们无法解决

  • 主题名称似乎总是
    channel\u name+.DLT
    ,因为我们无法确定如何使用像
    dlq
    这样的不同名称。我们为所有消费者使用一个
    dlq
    主题,这似乎不是Spring kafka默认DLT所期望的
  • 似乎我们需要在DLT上至少有与消费者主题相同数量的分区。否则,此解决方案将不起作用。虽然不确定如何管理,但对我们来说,这似乎不是一个实际的假设
  • 有没有一种方法可以像SpringCloudStream在幕后所做的那样利用SpringRetry?或者这需要单独实施?i、 e.根据
    最大尝试次数重试工作
    ,然后输入DLQ部分
  • 我可以看到,在示例中,弹簧执行器已用于通过
    this.endpoint.changeState(“polled”,State.PAUSED)
    this.endpoint.changeState(“polled,State.RESUMED)
    更新通道状态。为什么我们需要在暂停、重新提问等的同时这样做。不这样做的副作用是什么

    • 你的观察是正确的;错误处理绑定到线程

      您可以在代码中直接使用
      死信发布恢复器
      ,使DLQ的发布变得更容易(而不是输出通道)。这样,您将获得带有异常信息等的增强标题

      编辑

      这是一个例子;我暂停绑定以防止在“作业”正在运行时出现任何新的交付,而不是像您所做的那样重新请求交付

      @springboot应用程序
      @使能调度
      公共类SO67296258应用程序{
      公共静态void main(字符串[]args){
      SpringApplication.run(So67296258Application.class,args);
      }
      @豆子
      T
      
      spring.cloud.stream.pollable-source=polled
      spring.cloud.stream.bindings.polled-in-0.destination=polled
      spring.cloud.stream.bindings.polled-in-0.group=polled