[消息发送] 主Topic: hello-unicast-dlq-no-partition, 内容: 测试消息(主Topic: hello-unicast-dlq-no-partition)- 会触发死信[消息发送] 主Topic: hello-unicast-dlq-partition, 内容: 测试消息(主Topic: hello-unicast-dlq-partition)- 会触发死信
[消息发送] 主Topic: hello-broadcast-dlq-no-partition, 内容: 测试消息(主Topic: hello-broadcast-dlq-no-partition)- 会触发死信
[消息发送] 主Topic: hello-broadcast-dlq-partition, 内容: 测试消息(主Topic: hello-broadcast-dlq-partition)- 会触发死信[消息发送] 主Topic: hello-broadcast-wt-dlq, 内容: 测试消息(主Topic: hello-broadcast-wt-dlq)- 会触发死信[消息发送] 主Topic: hello-unicast-wt-dlq, 内容: 测试消息(主Topic: hello-unicast-wt-dlq)- 会触发死信消息: 测试消息(主Topic: hello-unicast-wt-dlq)- 会触发死信
2025-10-09 14:38:16.868 WARN 22640 --- [-unicast-wt-dlq] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 1/3): [消费失败] 模拟业务异常
2025-10-09 14:38:16.868 INFO 22640 --- [-unicast-wt-dlq] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Scheduled delay of 1000ms for event
消息: 测试消息(主Topic: hello-unicast-dlq-partition)- 会触发死信
消息: 测试消息(主Topic: hello-unicast-dlq-no-partition)- 会触发死信
2025-10-09 14:38:16.868 WARN 22640 --- [nPool-worker-18] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 1/3): [消费失败] 模拟业务异常
2025-10-09 14:38:16.868 WARN 22640 --- [nPool-worker-22] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 1/3): [消费失败] 模拟业务异常
2025-10-09 14:38:16.868 INFO 22640 --- [nPool-worker-18] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Scheduled delay of 1000ms for event
2025-10-09 14:38:16.868 INFO 22640 --- [nPool-worker-22] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Scheduled delay of 1000ms for event
消息: 测试消息(主Topic: hello-unicast-wt-dlq)- 会触发死信
消息: 测试消息(主Topic: hello-unicast-dlq-no-partition)- 会触发死信
2025-10-09 14:38:17.869 WARN 22640 --- [-unicast-wt-dlq] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 2/3): [消费失败] 模拟业务异常
消息: 测试消息(主Topic: hello-unicast-dlq-partition)- 会触发死信
2025-10-09 14:38:17.869 WARN 22640 --- [nPool-worker-22] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 2/3): [消费失败] 模拟业务异常
2025-10-09 14:38:17.869 INFO 22640 --- [nPool-worker-22] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Scheduled delay of 5000ms for event
2025-10-09 14:38:17.869 WARN 22640 --- [nPool-worker-18] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 2/3): [消费失败] 模拟业务异常
2025-10-09 14:38:17.869 INFO 22640 --- [nPool-worker-18] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Scheduled delay of 5000ms for event
2025-10-09 14:38:17.869 INFO 22640 --- [-unicast-wt-dlq] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Scheduled delay of 5000ms for event
消息: 测试消息(主Topic: hello-unicast-wt-dlq)- 会触发死信
消息: 测试消息(主Topic: hello-unicast-dlq-partition)- 会触发死信
消息: 测试消息(主Topic: hello-unicast-dlq-no-partition)- 会触发死信
2025-10-09 14:38:22.878 WARN 22640 --- [-unicast-wt-dlq] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 3/3): [消费失败] 模拟业务异常
2025-10-09 14:38:22.878 WARN 22640 --- [nPool-worker-18] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 3/3): [消费失败] 模拟业务异常
2025-10-09 14:38:22.878 WARN 22640 --- [nPool-worker-22] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Eventhandle failed (attempt 3/3): [消费失败] 模拟业务异常
2025-10-09 14:38:22.885 ERROR 22640 --- [nPool-worker-22] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Event failed after 3 retries, sent to DLQ
2025-10-09 14:38:22.885 ERROR 22640 --- [nPool-worker-18] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Event failed after 3 retries, sent to DLQ
2025-10-09 14:38:22.885 ERROR 22640 --- [-unicast-wt-dlq] c.t.s.e.p.k.d.DLQEventHandlerWrapper : Event failed after 3 retries, sent to DLQ
[死信捕获] 死信Topic: hello-unicast-wt-dlq_dlq_topic, 消息: 测试消息(主Topic: hello-unicast-dlq-no-partition)- 会触发死信
2025-10-09 14:38:22.891 INFO 22640 --- [ad | producer-1] c.t.s.e.p.k.d.DLQEventHandlerWrapper : 发送至死信队列!原始事件: {"filterKey":"key-0","timeStamp":1759991902880,"message":"测试消息(主Topic: hello-unicast-dlq-no-partition)- 会触发死信"},时间戳:1759991902884,主题: hello-unicast-wt-dlq_dlq_topic, 分区: 0, 已重试次数: 3,最后一次异常信息:[消费失败] 模拟业务异常
2025-10-09 14:38:22.891 INFO 22640 --- [ad | producer-1] c.t.s.e.p.k.d.DLQEventHandlerWrapper : 发送至死信队列!原始事件: {"filterKey":"key-1","timeStamp":1759991902880,"message":"测试消息(主Topic: hello-unicast-dlq-partition)- 会触发死信"},时间戳:1759991902884,主题: hello-unicast-wt-dlq_dlq_topic, 分区: 0, 已重试次数: 3,最后一次异常信息:[消费失败] 模拟业务异常
2025-10-09 14:38:22.892 INFO 22640 --- [ad | producer-1] c.t.s.e.p.k.d.DLQEventHandlerWrapper : 发送至死信队列!原始事件: {"filterKey":"key-5","timeStamp":1759991902880,"message":"测试消息(主Topic: hello-unicast-wt-dlq)- 会触发死信"},时间戳:1759991902884,主题: hello-unicast-wt-dlq_dlq_topic, 分区: 0, 已重试次数: 3,最后一次异常信息:[消费失败] 模拟业务异常
[死信捕获] 死信Topic: hello-unicast-wt-dlq_dlq_topic, 消息: 测试消息(主Topic: hello-unicast-dlq-partition)- 会触发死信
[死信捕获] 死信Topic: hello-unicast-wt-dlq_dlq_topic, 消息: 测试消息(主Topic: hello-unicast-wt-dlq)- 会触发死信@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
@ComponentScan(basePackages = {
"com.tplink.nbu.demo.basicspringboot",
"com.tplink.smb.eventcenter.port.kafka.deadletter",
"com.tplink.smb.eventcenter.api.config"
})
public class KafkaDemoApp implements CommandLineRunner {
@Autowired
private KafkaEventCenter eventCenter;
@Autowired
private DLQConfig deadLetterConfig;
// 所有测试主Topic(对应不同重载方法)
private static final List<String> TEST_MAIN_TOPICS = Arrays.asList(
"hello-unicast-dlq-no-partition", // registerUnicast(DLQ, 无Partition)
"hello-unicast-dlq-partition", // registerUnicast(DLQ+Partition)
"hello-broadcast-dlq-no-partition", // registerBroadcast(DLQ, 无Partition)
"hello-broadcast-dlq-partition", // registerBroadcast(DLQ+Partition)
"hello-broadcast-wt-dlq", // registerBroadcastWithoutThreadPool(DLQ)
"hello-unicast-wt-dlq" // registerUnicastWithoutThreadPool(DLQ)
);
public static void main(String[] args) {
SpringApplication.run(KafkaDemoApp.class, args);
}
@Override
public void run(String... args) throws Exception {
registerDLQConsumers(); // 注册所有带死信的消费者(模拟失败)
registerDLQListeners(); // 注册所有死信Topic的监听
sendTestMessagesToAllTopics();// 发送测试消息触发死信
}
/**
* 注册所有带死信的消费者(每个重载方法对应一个消费者)
*/
private void registerDLQConsumers() {
// 模拟消费失败的Handler(所有测试共用)
EventHandler failingHandler = event -> {
System.out.println(" 消息: " + event.getMessage());
throw new RuntimeException("[消费失败] 模拟业务异常 " );
};
// -------------------------- 1. registerUnicast(DLQ, 无PartitionAssignorMode) --------------------------
eventCenter.registerUnicast(
TEST_MAIN_TOPICS.get(0), // 主Topic
"group-unicast-dlq-no-part", // 消费者组(手动指定)
failingHandler,
ForkJoinPool.commonPool(), // 线程池
deadLetterConfig // 死信配置
);
// -------------------------- 2. registerUnicast(DLQ+PartitionAssignorMode) --------------------------
eventCenter.registerUnicast(
TEST_MAIN_TOPICS.get(1), // 主Topic
"group-unicast-dlq-part", // 消费者组
failingHandler,
ForkJoinPool.commonPool(), // 线程池
PartitionAssignorMode.COOPERATIVE_STICKY, // 分区策略
deadLetterConfig // 死信配置
);
// -------------------------- 3. registerBroadcast(DLQ, 无PartitionAssignorMode) --------------------------
eventCenter.registerBroadcast(
TEST_MAIN_TOPICS.get(2), // 主Topic
failingHandler,
ForkJoinPool.commonPool(), // 线程池
deadLetterConfig // 死信配置(groupId内部生成)
);
// -------------------------- 4. registerBroadcast(DLQ+PartitionAssignorMode) --------------------------
eventCenter.registerBroadcast(
TEST_MAIN_TOPICS.get(3), // 主Topic
failingHandler,
ForkJoinPool.commonPool(), // 线程池
PartitionAssignorMode.COOPERATIVE_STICKY, // 分区策略
deadLetterConfig // 死信配置(groupId内部生成)
);
// -------------------------- 5. registerBroadcastWithoutThreadPool(DLQ) --------------------------
eventCenter.registerBroadcastWithoutThreadPool(
TEST_MAIN_TOPICS.get(4), // 主Topic
failingHandler,
PartitionAssignorMode.COOPERATIVE_STICKY, // 分区策略
deadLetterConfig // 死信配置(无线程池,groupId内部生成)
);
// -------------------------- 6. registerUnicastWithoutThreadPool(DLQ) --------------------------
eventCenter.registerUnicastWithoutThreadPool(
TEST_MAIN_TOPICS.get(5), // 主Topic
"group-unicast-wt-dlq", // 消费者组(手动指定)
failingHandler,
PartitionAssignorMode.COOPERATIVE_STICKY, // 分区策略
deadLetterConfig // 死信配置(无线程池)
);
}
/**
* 注册所有死信Topic的监听(死信Topic规则:主Topic + "_dlq_topic")
*/
private void registerDLQListeners() {
for (String mainTopic : TEST_MAIN_TOPICS) {
String dlqTopic = mainTopic + "_dlq_topic"; // 死信Topic(与业务代码规则一致)
eventCenter.registerUnicast(
dlqTopic,
"dlq-group-" + mainTopic, // 死信消费者组(唯一即可)
event -> System.out.println("[死信捕获] 死信Topic: " + dlqTopic + ", 消息: " + event.getMessage()),
ForkJoinPool.commonPool()
);
}
}
/**
* 给所有主Topic发送测试消息(触发消费失败)
*/
private void sendTestMessagesToAllTopics() {
for (int i = 0; i < TEST_MAIN_TOPICS.size(); i++) {
String mainTopic = TEST_MAIN_TOPICS.get(i);
Event event = new Event(
"key-" + i,
"测试消息(主Topic: " + mainTopic + ")- 会触发死信"
);
eventCenter.send(mainTopic, event);
System.out.println("[消息发送] 主Topic: " + mainTopic + ", 内容: " + event.getMessage());
}
}
}@Slf4j
@RequiredArgsConstructor
public class DLQEventHandlerWrapper implements EventHandler {
// 原事件处理器
private final EventHandler delegate;
// DLQ配置
private final DLQConfig dlqConfig;
// 事件中心(用于发送死信)
private final KafkaEventCenter eventCenter;
private String lastException;
@Override
public void handleEvent(Event event) {
if (!dlqConfig.isEnabled()) {
delegate.handleEvent(event);
// 未启用DLQ,直接执行原逻辑
return;
}
int retryCount = 0;
boolean success = false;
while (retryCount < dlqConfig.getMaxRetries()) {
try {
delegate.handleEvent(event);
// 执行原事件处理
success = true;
break;
} catch (Exception e) {
retryCount++;
log.warn("Eventhandle failed (attempt {}/{}): {}",
retryCount, dlqConfig.getMaxRetries(), e.getMessage());
lastException=e.getMessage();
if (retryCount >= dlqConfig.getMaxRetries()) {
// 达到最大重试次数,发送死信
break;
}
// 计算重试延时(指数退避)
long delay = dlqConfig.getRetryStrategy().getNextDelay(retryCount);
try {
sendDelay(event, delay);
// 重试延时占位(后续实现)
Thread.sleep(delay);
// 临时用sleep模拟,后续替换为异步延时
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
log.error("Retry interrupted for event", ie);
break;
}
}
}
if (!success) {
sendToDLQ(event, retryCount,lastException);
log.error("Event failed after {} retries, sent to DLQ"
, dlqConfig.getMaxRetries());
}
}
/**
* 将事件发送至死信队列
*/
private void sendToDLQ(Event event,int retryCount,String lastException) {
String dlqTopic = dlqConfig.getDlqTopic();
// 构造自定义EventFuture回调,捕获发送结果
EventFuture dlqEventFuture = new EventFuture() {
@Override
public void onSuccess(EventCenterSendResult result) {
log.info("发送至死信队列!原始事件: {},时间戳:{},主题: {}, 分区: {}, 已重试次数: {},最后一次异常信息:{}",
event.toString(),result.getTimestamp(),result.getTopic(), result.getPartition(), retryCount, lastException);
}
@Override
public void onFailure(Throwable throwable) {
// 关键:记录重试次数和发送异常
log.error("死信消息发送失败!原始事件: {}, 已重试次数: {}, 异常信息: {}",
event.toString(), retryCount, throwable.getMessage(), throwable);
}
};
try {
// 调用带回调的send方法(匹配参数:topic, event, eventFuture)
eventCenter.send(dlqTopic, event, dlqEventFuture);
} catch (Exception e) {
// 处理send方法本身的异常(如参数错误)
log.error("调用死信发送方法时发生异常,已重试次数: {}", retryCount, e);
}
}
/**
* 重试延时占位方法(后续实现) 例如:用Redis/ZooKeeper实现延时队列,或用定时任务调度
*/
private void sendDelay(BaseEvent event, long delay) {
log.info("Scheduled delay of {}ms for event", delay);
// TODO: 后续实现真实延时逻辑(如:将事件存入延时队列,到期后重试)
}
} @Override
public void registerBroadcast(@Nonnull String topic, @Nonnull EventHandler handler,
@Nonnull ExecutorService executor) {
String groupId = idGenerator.createGroupId(topic);
registerUnicast(topic, groupId, handler, executor);
}
public void registerBroadcast(@Nonnull String topic, @Nonnull EventHandler handler,
@Nonnull ExecutorService executor, @Nonnull DLQConfig dlqConfig) {
String groupId = idGenerator.createGroupId(topic);
registerUnicast(topic, groupId, handler, executor, null, dlqConfig);
}
/**
* 注册处理广播事件的handler groupId使用UUID随机生成,确保唯一性。
*
* @param topic 主题
* @param handler 事件处理类
* @param executorService 上层传递的处理业务的线程池
* @param partitionAssignorMode 分区分配策略
*/
@Override
public void registerBroadcast(@Nonnull String topic, @Nonnull EventHandler handler,
@Nonnull ExecutorService executorService,
PartitionAssignorMode partitionAssignorMode) {
String groupId = idGenerator.createGroupId(topic);
registerUnicast(topic, groupId, handler, executorService, partitionAssignorMode);
}
public void registerBroadcast(@Nonnull String topic, @Nonnull EventHandler handler,
@Nonnull ExecutorService executorService,
@Nonnull PartitionAssignorMode partitionAssignorMode,
@Nonnull DLQConfig dlqConfig) {
String groupId = idGenerator.createGroupId(topic);
registerUnicast(topic, groupId, handler, executorService, partitionAssignorMode, dlqConfig);
}
/**
* 注册处理单播事件的handler 一个topic对应一个consumerId
*
* @param topic 主题
* @param groupId 消费者组Id,在同一个组里的消费者实现单播消费
* @param handler 事件处理类
* @param executor 上层传递的用于业务处理的线程池
*/
@Override
public void registerUnicast(@Nonnull String topic, @Nonnull String groupId, @Nonnull EventHandler handler,
@Nonnull ExecutorService executor) {
registerUnicast(topic, groupId, handler, executor, (PartitionAssignorMode) null);
}
public void registerUnicast(@Nonnull String topic, @Nonnull String groupId, @Nonnull EventHandler handler,
@Nonnull ExecutorService executor, @Nonnull DLQConfig dlqConfig) {
registerUnicast(topic, groupId, handler, executor, null,dlqConfig);
}
/**
* 注册处理单播Topic消息的handler,
*
* @param topic 主题
* @param groupId 消费者组Id
* @param handler 事件处理类
* @param executorService 上层传递的处理业务的线程池
* @param partitionAssignorMode 分区分配策略
*/
@Override
public void registerUnicast(@Nonnull String topic, @Nonnull String groupId, @Nonnull EventHandler handler,
@Nonnull ExecutorService executorService,
PartitionAssignorMode partitionAssignorMode) {
partitionAssignorMode = getPartitionAssignorMode(topic, groupId, partitionAssignorMode);
register(topic, groupId, handler, executorService, partitionAssignorMode);
}
public void registerUnicast(@Nonnull String topic, @Nonnull String groupId, @Nonnull EventHandler handler,
@Nonnull ExecutorService executorService,
PartitionAssignorMode partitionAssignorMode, @Nonnull DLQConfig dlqConfig) {
dlqConfig.setDlqTopic(topic + "_dlq_topic");
partitionAssignorMode = getPartitionAssignorMode(topic, groupId, partitionAssignorMode);
EventHandler wrappedHandler = new DLQEventHandlerWrapper(handler, dlqConfig, this);
register(topic, groupId, wrappedHandler, executorService, partitionAssignorMode);
}
/**
* 注册处理广播Topic消息的无业务线程池的Handler
*
* @param topic 主题
* @param handler 事件处理类
* @param partitionAssignorMode 分区分配策略
*/
@Override
public void registerBroadcastWithoutThreadPool(@Nonnull String topic, @Nonnull EventHandler handler,
PartitionAssignorMode partitionAssignorMode) {
String groupId = idGenerator.createGroupId(topic);
registerUnicastWithoutThreadPool(topic, groupId, handler, partitionAssignorMode);
}
public void registerBroadcastWithoutThreadPool(@Nonnull String topic, @Nonnull EventHandler handler,
@Nonnull PartitionAssignorMode partitionAssignorMode,
@Nonnull DLQConfig dlqConfig) {
String groupId = idGenerator.createGroupId(topic);
registerUnicastWithoutThreadPool(topic, groupId, handler, partitionAssignorMode, dlqConfig);
}
/**
* 注册处理单播Topic消息的无业务线程池的Handler
*
* @param topic 主题
* @param groupId 消费者组
* @param handler 事件处理类
* @param partitionAssignorMode 分区分配策略
*/
@Override
public void registerUnicastWithoutThreadPool(@Nonnull String topic, @Nonnull String groupId,
@Nonnull EventHandler handler,
PartitionAssignorMode partitionAssignorMode) {
partitionAssignorMode = getPartitionAssignorMode(topic, groupId, partitionAssignorMode);
register(topic, groupId, handler, null, partitionAssignorMode);
}
public void registerUnicastWithoutThreadPool(@Nonnull String topic, @Nonnull String groupId,
@Nonnull EventHandler handler,
@Nonnull PartitionAssignorMode partitionAssignorMode,
@Nonnull DLQConfig dlqConfig) {
// 设置DLQ主题(默认规则:原主题+_dlq_topic)
dlqConfig.setDlqTopic(topic + "_dlq_topic");
// 解析默认分区分配模式(处理null场景)
partitionAssignorMode = getPartitionAssignorMode(topic, groupId, partitionAssignorMode);
// 包装Handler,添加DLQ逻辑
EventHandler wrappedHandler = new DLQEventHandlerWrapper(handler, dlqConfig, this);
// 调用核心注册(executorService传null,无线程池)
register(topic, groupId, wrappedHandler, null, partitionAssignorMode);
}
请根据以上内容分析为何日志中没有出现几个重载的广播方法对应的死信消息捕获
最新发布