feat: 事件重试 step 2
This commit is contained in:
parent
0ef2eba7bc
commit
1496b79b70
@ -1,5 +1,6 @@
|
|||||||
package cn.axzo.foundation.event.support;
|
package cn.axzo.foundation.event.support;
|
||||||
|
|
||||||
|
import cn.axzo.foundation.event.support.consumer.EventHandledWrapper;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -8,6 +9,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
@ -20,6 +22,10 @@ public class EventHandlerContext {
|
|||||||
/** 用于打印日志 */
|
/** 用于打印日志 */
|
||||||
String name;
|
String name;
|
||||||
EventHandler eventHandler;
|
EventHandler eventHandler;
|
||||||
|
/** 消费失败的回调 */
|
||||||
|
Consumer<EventHandledWrapper> exceptionHandler;
|
||||||
|
/** 消费成功的回调 */
|
||||||
|
Consumer<EventHandledWrapper> successHandler;
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return Optional.ofNullable(Strings.emptyToNull(name)).orElse(eventHandler.getClass().getSimpleName());
|
return Optional.ofNullable(Strings.emptyToNull(name)).orElse(eventHandler.getClass().getSimpleName());
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,13 +33,11 @@ import java.util.stream.Collectors;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class DefaultEventConsumer implements EventConsumer {
|
public class DefaultEventConsumer implements EventConsumer {
|
||||||
private final EventHandlerRepository handlerRepository;
|
private final EventHandlerRepository handlerRepository;
|
||||||
private final Consumer<EventWrapper> consumeCallback;
|
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
public DefaultEventConsumer(EventHandlerRepository handlerRepository, Consumer<EventWrapper> consumeCallback) {
|
public DefaultEventConsumer(EventHandlerRepository handlerRepository) {
|
||||||
this.handlerRepository = Optional.ofNullable(handlerRepository).orElseGet(() -> EventHandlerRepository.builder()
|
this.handlerRepository = Optional.ofNullable(handlerRepository).orElseGet(() -> EventHandlerRepository.builder()
|
||||||
.build());
|
.build());
|
||||||
this.consumeCallback = consumeCallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -83,15 +80,6 @@ public class DefaultEventConsumer implements EventConsumer {
|
|||||||
Throwables.throwIfUnchecked(ex);
|
Throwables.throwIfUnchecked(ex);
|
||||||
throw new RuntimeException("process event fail", ex);
|
throw new RuntimeException("process event fail", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumeCallback != null) {
|
|
||||||
consumeCallback.accept(EventWrapper.builder()
|
|
||||||
.event(event)
|
|
||||||
.consumer(this)
|
|
||||||
.isHandled(handled)
|
|
||||||
.context(context)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -85,15 +85,6 @@ public interface EventConsumer {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Builder
|
|
||||||
@Getter
|
|
||||||
class EventWrapper {
|
|
||||||
private Event event;
|
|
||||||
private EventConsumer consumer;
|
|
||||||
private boolean isHandled;
|
|
||||||
private Context context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
class Context {
|
class Context {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package cn.axzo.foundation.event.support.consumer;
|
package cn.axzo.foundation.event.support.consumer;
|
||||||
|
|
||||||
import cn.axzo.foundation.event.support.Event;
|
import cn.axzo.foundation.event.support.Event;
|
||||||
import cn.axzo.foundation.event.support.EventHandlerContext;
|
|
||||||
import cn.axzo.foundation.exception.BusinessException;
|
import cn.axzo.foundation.exception.BusinessException;
|
||||||
import cn.axzo.foundation.result.ResultCode;
|
import cn.axzo.foundation.result.ResultCode;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -15,19 +14,27 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class EventExceptionContext {
|
public class EventHandledWrapper {
|
||||||
Throwable exception;
|
|
||||||
String msg;
|
|
||||||
Event event;
|
Event event;
|
||||||
EventConsumer.Context consumerContext;
|
EventConsumer.Context consumeContext;
|
||||||
EventHandlerContext handlerContext;
|
|
||||||
|
|
||||||
public void doPrint() {
|
/** handler的唯一索引, 重试时需要 */
|
||||||
|
String handlerKey;
|
||||||
|
/** 用于打印日志 */
|
||||||
|
String name;
|
||||||
|
|
||||||
|
/** 如果抛出异常则有值 */
|
||||||
|
Throwable exception;
|
||||||
|
|
||||||
|
public void doPrintException() {
|
||||||
|
if (exception == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (BusinessException.class.isAssignableFrom(exception.getClass()) &&
|
if (BusinessException.class.isAssignableFrom(exception.getClass()) &&
|
||||||
!ResultCode.PROCESS_TIMEOUT.getCode().equals(((BusinessException) exception).getErrorCode())) {
|
!ResultCode.PROCESS_TIMEOUT.getCode().equals(((BusinessException) exception).getErrorCode())) {
|
||||||
log.warn("MQ, handle warning {}", msg, exception);
|
log.warn("MQ, handle warning {}", event.toPrettyJsonString(), exception);
|
||||||
} else {
|
} else {
|
||||||
log.error("MQ, handle error {}", msg, exception);
|
log.error("MQ, handle error {}", event.toPrettyJsonString(), exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ import java.util.stream.Collectors;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class EventHandlerRepository {
|
public class EventHandlerRepository {
|
||||||
final protected ListMultimap<Event.EventCode, EventHandlerContext> handlers = ArrayListMultimap.create();
|
final protected ListMultimap<Event.EventCode, EventHandlerContext> handlers = ArrayListMultimap.create();
|
||||||
private final Consumer<EventExceptionContext> exceptionHandler;
|
private final Consumer<EventHandledWrapper> DEFAULT_EXCEPTION_HANDLER = EventHandledWrapper::doPrintException;
|
||||||
private AntPathMatcher antPathMatcher;
|
private AntPathMatcher antPathMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,13 +43,11 @@ public class EventHandlerRepository {
|
|||||||
|
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
public EventHandlerRepository(Consumer<EventExceptionContext> exceptionHandler,
|
public EventHandlerRepository(boolean supportPattern,
|
||||||
boolean supportPattern,
|
|
||||||
Boolean logEnabled,
|
Boolean logEnabled,
|
||||||
Predicate<Event> logFilter,
|
Predicate<Event> logFilter,
|
||||||
Long logElapsedThreshold,
|
Long logElapsedThreshold,
|
||||||
Long maxAllowElapsedMillis) {
|
Long maxAllowElapsedMillis) {
|
||||||
this.exceptionHandler = Optional.ofNullable(exceptionHandler).orElse(EventExceptionContext::doPrint);
|
|
||||||
|
|
||||||
this.logEnabled = Optional.ofNullable(logEnabled).orElse(Boolean.TRUE);
|
this.logEnabled = Optional.ofNullable(logEnabled).orElse(Boolean.TRUE);
|
||||||
this.logFilter = logFilter;
|
this.logFilter = logFilter;
|
||||||
@ -142,13 +140,7 @@ public class EventHandlerRepository {
|
|||||||
log.warn(msg);
|
log.warn(msg);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
handleException(EventExceptionContext.builder()
|
handleException(event, context, handler, ex);
|
||||||
.event(event)
|
|
||||||
.handlerContext(handler)
|
|
||||||
.msg(event.toPrettyJsonString())
|
|
||||||
.exception(ex)
|
|
||||||
.consumerContext(context)
|
|
||||||
.build());
|
|
||||||
} finally {
|
} finally {
|
||||||
// stopwatch必须reset(),否则下一次stopwatch.start()会报错
|
// stopwatch必须reset(),否则下一次stopwatch.start()会报错
|
||||||
stopwatch.reset();
|
stopwatch.reset();
|
||||||
@ -207,9 +199,21 @@ public class EventHandlerRepository {
|
|||||||
return canHandle(event.getEventCode());
|
return canHandle(event.getEventCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleException(EventExceptionContext context) {
|
private void handleException(Event event,
|
||||||
if (exceptionHandler != null) {
|
EventConsumer.Context consumerContext,
|
||||||
exceptionHandler.accept(context);
|
EventHandlerContext context,
|
||||||
|
Exception exception) {
|
||||||
|
EventHandledWrapper wrapper = EventHandledWrapper.builder()
|
||||||
|
.event(event)
|
||||||
|
.consumeContext(consumerContext)
|
||||||
|
.name(context.getName())
|
||||||
|
.handlerKey(context.getHandlerKey())
|
||||||
|
.exception(exception)
|
||||||
|
.build();
|
||||||
|
if (context.getExceptionHandler() != null) {
|
||||||
|
context.getExceptionHandler().accept(wrapper);
|
||||||
|
} else {
|
||||||
|
DEFAULT_EXCEPTION_HANDLER.accept(wrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +238,7 @@ public class EventHandlerRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean getLogEnabled(Event event, EventConsumer.Context context) {
|
public boolean getLogEnabled(Event event, EventConsumer.Context context) {
|
||||||
if (handlers.containsKey(EventHeaders.DISABLE_LOG_HEADER)) {
|
if (context.getHeaders().containsKey(EventHeaders.DISABLE_LOG_HEADER)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// consumer显式声明了日志开关,则直接使用
|
// consumer显式声明了日志开关,则直接使用
|
||||||
|
|||||||
@ -26,43 +26,42 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
|
||||||
public class RetryableEventConsumer {
|
public class RetryableEventConsumer implements EventConsumer {
|
||||||
EventConsumer eventConsumer;
|
EventConsumer eventConsumer;
|
||||||
Map<String, RetryEvent> retryContexts = Maps.newConcurrentMap();
|
Map<String, RetryEvent> retryContexts = Maps.newConcurrentMap();
|
||||||
|
|
||||||
private static final Set<Class<? extends Throwable>> DEFAULT_EXCLUDE_EXCEPTIONS = ImmutableSet.of(BusinessException.class);
|
private static final Set<Class<? extends Throwable>> DEFAULT_EXCLUDE_EXCEPTIONS = ImmutableSet.of(BusinessException.class);
|
||||||
private static final BackoffPolicy DEFAULT_BACKOFFPOLICY = ExponentialBackOffPolicy.builder().build();
|
private static final BackoffPolicy DEFAULT_BACKOFFPOLICY = ExponentialBackOffPolicy.builder().build();
|
||||||
|
|
||||||
@Builder
|
private final Consumer<EventHandledWrapper> exceptionHandler;
|
||||||
public RetryableEventConsumer(Consumer<RetryContext> consumer) {
|
|
||||||
this.eventConsumer = DefaultEventConsumer.builder()
|
|
||||||
.handlerRepository(EventHandlerRepository.builder()
|
|
||||||
.exceptionHandler(context -> {
|
|
||||||
context.doPrint();
|
|
||||||
if (Strings.isNullOrEmpty(context.getHandlerContext().getHandlerKey())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RetryEvent retryEvent = retryContexts.get(context.getHandlerContext().getHandlerKey());
|
|
||||||
Set<Class<? extends Throwable>> excludeExceptions = retryEvent.getExcludeExceptions();
|
|
||||||
if (excludeExceptions.stream().anyMatch(e -> e.isAssignableFrom(context.getException().getClass()))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Integer currentRetryCount = getRetryCount(context.getConsumerContext());
|
|
||||||
|
|
||||||
Optional<Long> nextRetryMillis = retryEvent.getBackoffPolicy().getNextRetryMillis(currentRetryCount);
|
public RetryableEventConsumer(EventConsumer eventConsumer, Consumer<RetryContext> consumer) {
|
||||||
if (!nextRetryMillis.isPresent()) {
|
Preconditions.checkNotNull(eventConsumer);
|
||||||
return;
|
this.eventConsumer = eventConsumer;
|
||||||
}
|
exceptionHandler = context -> {
|
||||||
LocalDateTime nextTriggerTime = Instant.ofEpochMilli(nextRetryMillis.get()).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
context.doPrintException();
|
||||||
consumer.accept(RetryContext.builder()
|
if (Strings.isNullOrEmpty(context.getHandlerKey())) {
|
||||||
.event(context.getEvent())
|
return;
|
||||||
.retryKey(context.getHandlerContext().getHandlerKey())
|
}
|
||||||
.nextTriggerTime(nextTriggerTime)
|
RetryEvent retryEvent = retryContexts.get(context.getHandlerKey());
|
||||||
.currentRetryCount(currentRetryCount)
|
Set<Class<? extends Throwable>> excludeExceptions = retryEvent.getExcludeExceptions();
|
||||||
.build());
|
if (excludeExceptions.stream().anyMatch(e -> e.isAssignableFrom(context.getException().getClass()))) {
|
||||||
})
|
return;
|
||||||
.build())
|
}
|
||||||
.build();
|
Integer currentRetryCount = getRetryCount(context.getConsumeContext());
|
||||||
|
|
||||||
|
Optional<Long> nextRetryMillis = retryEvent.getBackoffPolicy().getNextRetryMillis(currentRetryCount);
|
||||||
|
if (!nextRetryMillis.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LocalDateTime nextTriggerTime = Instant.ofEpochMilli(nextRetryMillis.get()).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||||
|
consumer.accept(RetryContext.builder()
|
||||||
|
.event(context.getEvent())
|
||||||
|
.retryKey(context.getHandlerKey())
|
||||||
|
.nextTriggerTime(nextTriggerTime)
|
||||||
|
.currentRetryCount(currentRetryCount)
|
||||||
|
.build());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean registerHandler(RetryEvent retryEvent) {
|
public Boolean registerHandler(RetryEvent retryEvent) {
|
||||||
@ -72,6 +71,7 @@ public class RetryableEventConsumer {
|
|||||||
.eventHandler(retryEvent.getEventHandler())
|
.eventHandler(retryEvent.getEventHandler())
|
||||||
.name(retryEvent.getName())
|
.name(retryEvent.getName())
|
||||||
.handlerKey(retryEvent.getRetryKey())
|
.handlerKey(retryEvent.getRetryKey())
|
||||||
|
.exceptionHandler(exceptionHandler)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +83,21 @@ public class RetryableEventConsumer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean registerHandler(EventHandlerContext context) {
|
||||||
|
return registerHandler(RetryEvent.builder()
|
||||||
|
.eventCode(context.getEventCode())
|
||||||
|
.eventHandler(context.getEventHandler())
|
||||||
|
.name(context.getName())
|
||||||
|
.retryKey(context.getHandlerKey())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEvent(String message, Context context) {
|
||||||
|
return eventConsumer.onEvent(message, context);
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import com.google.common.collect.ImmutableRangeMap;
|
import com.google.common.collect.ImmutableRangeMap;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
import com.google.common.collect.RangeMap;
|
import com.google.common.collect.RangeMap;
|
||||||
|
import lombok.Builder;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -40,8 +41,9 @@ public class RocketRetryableEventConsumer extends RetryableEventConsumer {
|
|||||||
.put(Range.openClosed(TimeUnit.MINUTES.toMillis(60), Long.MAX_VALUE), 18)
|
.put(Range.openClosed(TimeUnit.MINUTES.toMillis(60), Long.MAX_VALUE), 18)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public RocketRetryableEventConsumer(EventProducer eventProducer, String retryTopic) {
|
@Builder
|
||||||
super(retryContext -> {
|
public RocketRetryableEventConsumer(EventProducer eventProducer, EventConsumer eventConsumer, String retryTopic) {
|
||||||
|
super(eventConsumer, retryContext -> {
|
||||||
/** 将延迟毫秒转换为rocketMq的延迟级别 */
|
/** 将延迟毫秒转换为rocketMq的延迟级别 */
|
||||||
Integer delayLevel = LEVEL_MAP.get(retryContext.getNextDelayMillis());
|
Integer delayLevel = LEVEL_MAP.get(retryContext.getNextDelayMillis());
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user