28.Axon框架-事件(六)

44 阅读4分钟

Axon框架-事件(六)

1.事件失败时的选择

1768414717495.png

2.什么是死信队列

为EventProcessor配置错误处理时,你可能需要考虑使用死信队列(Dead-Letter Queue,简称DLQ) 来停放无法处理的事件

死信队列会将失败事件存入队列,你可后续决定是否重新处理。此外,它会暂停同一序列中后续事件的处理,直至失败事件处理成功,避免因前序事件失败导致后续事件基于不一致状态执行

3.有序死信队列

介绍

Axon Framework的EventProcessor即使在并行处理配置下,也会维护同一序列内的事件顺序,典型场景是同一聚合(Aggregate)的事件需按发布顺序处理。若仅将失败事件放入死信队列,会导致后续事件基于不一致状态执行

因此,事件的死信队列必须具备入队失败事件及同一序列后续所有事件的能力,Axon支持的死信队列均为有序死信队列

其设计核心是通过排序策略(Sequencing Policy)为每个事件分配序列id,从而实现失败序列全量入队,确保事件顺序不被破坏

1768414966551.png

实现类

1768415015495.png

4.幂等性

1768415185563.png

5.约束

  1. 不同ProcessingGroup不能共享死信队列,需为每个要启用该功能的ProcessingGroup配置独立实例
  2. 死信队列不支持Saga:由于无法为Saga提供有序死信处理机制,且Saga的关联关系随事件变化,无法确定事件是否属于某个Saga,因此当前不支持为Saga配置死信队列

6.配置

# 启用指定的多个ProcessingGroup的死信队列
axon.eventhandling.processors.my-processing-group.dlq.enabled=true

7.重试处理

介绍

解决失败事件的根本原因后,可通过SequencedDeadLetterProcessor处理死信队列中的事件,该接口会处理整个死信序列(而非单个事件),确保事件顺序一致

1768415651325.png

重试失败处理

若死信处理仍失败,事件会重新入队,具体行为由 入队策略(Enqueue Policy)决定

重试示例

重试指定类型的死信序列
@Component
@RequiredArgsConstructor
public class DeadletterProcessor {

    private final EventProcessingConfiguration eventProcessingConfig;

    // 重试处理组中所有ErrorEvent类型的死信序列
    public void retryErrorEventSequence(String processingGroup) {
        eventProcessingConfig.sequencedDeadLetterProcessor(processingGroup)
              .ifPresent(processor -> processor.process(
                      deadLetter -> deadLetter.message().getPayload() instanceof ErrorEvent
              ));
    }
}
重试最旧的死信序列
@Component
@RequiredArgsConstructor
public class DeadletterProcessor {

    private final EventProcessingConfiguration eventProcessingConfig;

    public void retryOldestSequence(String processingGroup) {
        eventProcessingConfig.sequencedDeadLetterProcessor(processingGroup)
              .ifPresent(SequencedDeadLetterProcessor::processAny);
    }
}
重试所有死信序列
@Component
@RequiredArgsConstructor
public class DeadletterProcessor {

    private final EventProcessingConfiguration eventProcessingConfig;

    public void retryAllSequences(String processingGroup) {
        // 获取指定处理组的死信队列
        Optional<SequencedDeadLetterQueue<EventMessage<?>>> dlq = 
                eventProcessingConfig.deadLetterQueue(processingGroup);
        if (dlq.isEmpty()) {
            throw new IllegalArgumentException("处理组" + processingGroup + "未配置死信队列");
        }

        // 获取所有死信序列
        Iterable<Iterable<DeadLetter<? extends EventMessage<?>>>> deadLetterSequences = dlq.get().deadLetters();

        // 遍历所有序列并重试
        for (Iterable<DeadLetter<? extends EventMessage<?>>> sequence : deadLetterSequences) {
            Iterator<DeadLetter<? extends EventMessage<?>>> iterator = sequence.iterator();
            if (!iterator.hasNext()) {
                continue;
            }
            // 仅需通过序列首事件的ID过滤
            String firstEventId = iterator.next().message().getIdentifier();
            eventProcessingConfig.sequencedDeadLetterProcessor(processingGroup)
                  .ifPresent(processor -> processor.process(
                          deadLetter -> deadLetter.message().getIdentifier().equals(firstEventId)
                  ));
        }
    }
}

DeadLetter

介绍

EventHandler可注入DeadLetter参数,判断当前处理的是否为死信事件(null表示非死信事件),该参数暴露失败原因、诊断信息等属性

1768416118574.png

示例
@ProcessingGroup("my-processing-group")
@Component
public class MyEventHandler {
    // 省略依赖注入...

    @EventHandler
    public void on(SomeEvent event, DeadLetter<EventMessage<SomeEvent>> deadLetter) {
        if (deadLetter != null) {
            // 死信事件处理逻辑(如打印失败原因、补充诊断信息)
            Throwable cause = deadLetter.cause().orElseThrow(() -> new RuntimeException("死信事件无明确失败原因"));
            System.out.println("处理死信事件:" + event.getId() + ",失败原因:" + cause.getMessage());
        } else {
            // 普通事件处理逻辑
        }
    }
}
属性

1768416055798.png

8.入队策略

介绍

默认情况下,事件处理失败会直接入队,但你可能需要自定义规则(如排除特定异常、限制重试次数),此时可配置入队策略(EnqueuePolicy)

接口核心参数

  1. letter:死信事件
  2. cause:失败原因

自定义

/**
 * 自定义入队策略:排除空指针异常,限制重试次数为10次
 */
public class RetryLimitEnqueuePolicy implements EnqueuePolicy<EventMessage<?>> {

    @Override
    public EnqueueDecision<EventMessage<?>> decide(DeadLetter<? extends EventMessage<?>> letter, Throwable cause) {
        // 空指针异常直接丢弃,不入队
        if (cause instanceof NullPointerException) {
            return Decisions.doNotEnqueue();
        }

        // 从诊断元数据中获取已重试次数(初始为-1)
        int retries = (int) letter.diagnostics().getOrDefault("retries", -1);
        // 核心事件(如ErrorEvent)直接入队
        if (letter.message().getPayload() instanceof ErrorEvent) {
            return Decisions.enqueue(cause);
        }
        // 重试次数<10,继续入队并递增重试次数
        if (retries < 10) {
            return Decisions.requeue(cause, l -> l.diagnostics().and("retries", retries + 1));
        }

        // 重试次数耗尽,移除死信事件
        return Decisions.evict();
    }
}

配置

@Configuration
public class AxonConfig {
    // 省略其他配置方法...
    @Bean
    public ConfigurerModule enqueuePolicyConfigurerModule() {
        String processingGroup = "my-processing-group";
        return configurer -> configurer.eventProcessing()
                                       .registerDeadLetterPolicy(processingGroup, config -> new RetryLimitEnqueuePolicy());
    }
}