From 759d9082069bbc5d0aa295016fdb66ae1a1564f7 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 23 Apr 2021 21:24:23 +0800 Subject: [PATCH 01/11] Abstract EventErrorHandler --- .../Kafka/KafkaDistributedEventBus.cs | 20 +++-- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 90 +++++++++++++++++++ .../RabbitMq/RabbitMqDistributedEventBus.cs | 5 +- .../Rebus/RebusDistributedEventBus.cs | 5 +- .../Volo/Abp/EventBus/AbpEventBusOptions.cs | 21 +++++ .../AbpEventBusRetryStrategyOptions.cs | 10 +++ .../Volo/Abp/EventBus/EventBusBase.cs | 27 ++++-- .../Abp/EventBus/EventErrorHandlerBase.cs | 57 ++++++++++++ .../EventBus/EventExecutionErrorContext.cs | 23 +++++ .../Volo/Abp/EventBus/IEventErrorHandler.cs | 9 ++ .../Volo/Abp/EventBus/Local/LocalEventBus.cs | 24 +++-- .../EventBus/Local/LocalEventErrorHandler.cs | 71 +++++++++++++++ .../Abp/EventBus/Local/LocalEventMessage.cs | 20 +++++ .../Volo/Abp/Kafka/IKafkaMessageConsumer.cs | 2 + .../Volo/Abp/Kafka/IKafkaSerializer.cs | 2 + .../Volo/Abp/Kafka/KafkaMessageConsumer.cs | 3 +- .../Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs | 5 ++ .../Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs | 2 + .../RabbitMQ/Utf8JsonRabbitMqSerializer.cs | 7 +- 19 files changed, 369 insertions(+), 34 deletions(-) create mode 100644 framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index fd6d773952..46eb1c0827 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -6,8 +6,10 @@ using System.Threading.Tasks; using Confluent.Kafka; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Local; using Volo.Abp.Kafka; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -34,8 +36,9 @@ namespace Volo.Abp.EventBus.Kafka IKafkaMessageConsumerFactory messageConsumerFactory, IOptions abpDistributedEventBusOptions, IKafkaSerializer serializer, - IProducerPool producerPool) - : base(serviceScopeFactory, currentTenant) + IProducerPool producerPool, + IEventErrorHandler errorHandler) + : base(serviceScopeFactory, currentTenant, errorHandler) { AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; @@ -54,6 +57,7 @@ namespace Volo.Abp.EventBus.Kafka AbpKafkaEventBusOptions.GroupId, AbpKafkaEventBusOptions.ConnectionName); + Consumer.Consume(); Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); @@ -68,9 +72,10 @@ namespace Volo.Abp.EventBus.Kafka return; } - var eventData = Serializer.Deserialize(message.Value, eventType); + var eventMessage = Serializer.Deserialize(message.Value); - await TriggerHandlersAsync(eventType, eventData); + await TriggerHandlersAsync(eventType, eventMessage, + context => { context.SetProperty(KafkaEventErrorHandler.HeadersKey, message.Headers); }); } public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class @@ -147,6 +152,11 @@ namespace Volo.Abp.EventBus.Kafka } public override async Task PublishAsync(Type eventType, object eventData) + { + await PublishAsync(eventType, eventData, null); + } + + public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); @@ -157,7 +167,7 @@ namespace Volo.Abp.EventBus.Kafka AbpKafkaEventBusOptions.TopicName, new Message { - Key = eventName, Value = body + Key = eventName, Value = body, Headers = headers }); } diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs new file mode 100644 index 0000000000..65c8561149 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -0,0 +1,90 @@ +using System.Threading.Tasks; +using Confluent.Kafka; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Kafka; + +namespace Volo.Abp.EventBus.Kafka +{ + public class KafkaEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + public const string HeadersKey = "headers"; + public const string RetryIndexKey = "retryIndex"; + + protected IKafkaSerializer Serializer { get; } + protected KafkaDistributedEventBus EventBus { get; } + protected IProducerPool ProducerPool { get; } + protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } + + protected string ErrorTopicName { get; } + + public KafkaEventErrorHandler( + IOptions options, + IKafkaSerializer serializer, + KafkaDistributedEventBus eventBus, + IKafkaMessageConsumerFactory consumerFactory, + IProducerPool producerPool, + IOptions abpKafkaEventBusOptions) : base(options) + { + Serializer = serializer; + EventBus = eventBus; + ProducerPool = producerPool; + AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; + + ErrorTopicName = options.Value.ErrorQueue ?? abpKafkaEventBusOptions.Value.TopicName + "_error"; + consumerFactory.Create(ErrorTopicName, string.Empty, abpKafkaEventBusOptions.Value.ConnectionName); + } + + protected override async Task Retry(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions.IntervalMillisecond > 0) + { + await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); + } + + var headers = context.GetProperty(HeadersKey) ?? new Headers(); + var index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + + headers.Remove(RetryIndexKey); + headers.Add(RetryIndexKey, Serializer.Serialize(++index)); + + await EventBus.PublishAsync(context.EventType, context.EventData, headers); + } + + protected override async Task MoveToErrorQueue(EventExecutionErrorContext context) + { + var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); + var eventName = EventNameAttribute.GetNameOrDefault(context.EventType); + var body = Serializer.Serialize(context.EventData); + + await producer.ProduceAsync( + AbpKafkaEventBusOptions.TopicName, + new Message + { + Key = eventName, Value = body, + Headers = new Headers {{"exceptions", Serializer.Serialize(context.Exceptions)}} + }); + } + + protected override bool ShouldRetry(EventExecutionErrorContext context) + { + if (!base.ShouldRetry(context)) + { + return false; + } + + var headers = context.GetProperty(HeadersKey); + var index = 1; + + if (headers == null) + { + return true; + } + + index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + + return Options.RetryStrategyOptions.Count < index; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index e8c7fc6c5b..5baeda6d9f 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -41,8 +41,9 @@ namespace Volo.Abp.EventBus.RabbitMq IServiceScopeFactory serviceScopeFactory, IOptions distributedEventBusOptions, IRabbitMqMessageConsumerFactory messageConsumerFactory, - ICurrentTenant currentTenant) - : base(serviceScopeFactory, currentTenant) + ICurrentTenant currentTenant, + IEventErrorHandler errorHandler) + : base(serviceScopeFactory, currentTenant, errorHandler) { ConnectionPool = connectionPool; Serializer = serializer; diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index ce3549646e..97bedaf8fb 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -30,8 +30,9 @@ namespace Volo.Abp.EventBus.Rebus ICurrentTenant currentTenant, IBus rebus, IOptions abpDistributedEventBusOptions, - IOptions abpEventBusRebusOptions) : - base(serviceScopeFactory, currentTenant) + IOptions abpEventBusRebusOptions, + IEventErrorHandler errorHandler) : + base(serviceScopeFactory, currentTenant, errorHandler) { Rebus = rebus; AbpRebusEventBusOptions = abpEventBusRebusOptions.Value; diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs new file mode 100644 index 0000000000..18d3290c25 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs @@ -0,0 +1,21 @@ +using System; + +namespace Volo.Abp.EventBus +{ + public class AbpEventBusOptions + { + public bool EnabledErrorHandle { get; set; } + + public Func ErrorHandleSelector { get; set; } + + public string ErrorQueue { get; set; } + + public AbpEventBusRetryStrategyOptions RetryStrategyOptions { get; set; } + + public void UseRetryStrategy(Action action = null) + { + RetryStrategyOptions = new AbpEventBusRetryStrategyOptions(); + action?.Invoke(RetryStrategyOptions); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs new file mode 100644 index 0000000000..5762e8d7a0 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.EventBus +{ + public class AbpEventBusRetryStrategyOptions + { + + public int IntervalMillisecond { get; set; } = 3000; + + public int Count { get; set; } = 3; + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index 80d4134db2..e505f30ccb 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Collections; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Local; using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; @@ -19,10 +20,16 @@ namespace Volo.Abp.EventBus protected ICurrentTenant CurrentTenant { get; } - protected EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant) + protected IEventErrorHandler ErrorHandler { get; } + + protected EventBusBase( + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant, + IEventErrorHandler errorHandler) { ServiceScopeFactory = serviceScopeFactory; CurrentTenant = currentTenant; + ErrorHandler = errorHandler; } /// @@ -89,7 +96,7 @@ namespace Volo.Abp.EventBus /// public abstract Task PublishAsync(Type eventType, object eventData); - public virtual async Task TriggerHandlersAsync(Type eventType, object eventData) + public virtual async Task TriggerHandlersAsync(Type eventType, object eventData, Action onErrorAction = null) { var exceptions = new List(); @@ -97,16 +104,13 @@ namespace Volo.Abp.EventBus if (exceptions.Any()) { - if (exceptions.Count == 1) - { - exceptions[0].ReThrow(); - } - - throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); + var context = new EventExecutionErrorContext(exceptions, eventData, eventType); + onErrorAction?.Invoke(context); + await ErrorHandler.Handle(context); } } - protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData, List exceptions) + protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData , List exceptions) { await new SynchronizationContextRemover(); @@ -217,6 +221,11 @@ namespace Volo.Abp.EventBus }; } + protected virtual void OnErrorHandle(EventExecutionErrorContext context) + { + + } + protected class EventTypeWithEventHandlerFactories { public Type EventType { get; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs new file mode 100644 index 0000000000..14a27c4221 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; + +namespace Volo.Abp.EventBus +{ + public abstract class EventErrorHandlerBase : IEventErrorHandler + { + protected AbpEventBusOptions Options { get; } + + public EventErrorHandlerBase(IOptions options) + { + Options = options.Value; + } + + public virtual async Task Handle(EventExecutionErrorContext context) + { + if (!ShouldHandle(context)) + { + return; + } + + if (ShouldRetry(context)) + { + await Retry(context); + return; + } + + await MoveToErrorQueue(context); + } + + protected abstract Task Retry(EventExecutionErrorContext context); + + protected abstract Task MoveToErrorQueue(EventExecutionErrorContext context); + + protected virtual bool ShouldHandle(EventExecutionErrorContext context) + { + if (!Options.EnabledErrorHandle) + { + return false; + } + + if (Options.ErrorHandleSelector != null) + { + return Options.ErrorHandleSelector.Invoke(context.EventType); + } + + return false; + } + + protected virtual bool ShouldRetry(EventExecutionErrorContext context) + { + return Options.RetryStrategyOptions == null && false; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs new file mode 100644 index 0000000000..cf40102579 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.EventBus.Local; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.EventBus +{ + public class EventExecutionErrorContext : ExtensibleObject + { + public IReadOnlyList Exceptions { get; } + + public object EventData { get; } + + public Type EventType { get; } + + public EventExecutionErrorContext(List exceptions, object eventData, Type eventType) + { + Exceptions = exceptions; + EventData = eventData; + EventType = eventType; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs new file mode 100644 index 0000000000..27d4951ead --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.EventBus +{ + public interface IEventErrorHandler + { + Task Handle(EventExecutionErrorContext context); + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs index 8c7bef6f2d..0a0ae4f378 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -31,8 +32,9 @@ namespace Volo.Abp.EventBus.Local public LocalEventBus( IOptions options, IServiceScopeFactory serviceScopeFactory, - ICurrentTenant currentTenant) - : base(serviceScopeFactory, currentTenant) + ICurrentTenant currentTenant, + IEventErrorHandler errorHandler) + : base(serviceScopeFactory, currentTenant, errorHandler) { Options = options.Value; Logger = NullLogger.Instance; @@ -119,19 +121,15 @@ namespace Volo.Abp.EventBus.Local public override async Task PublishAsync(Type eventType, object eventData) { - var exceptions = new List(); - - await TriggerHandlersAsync(eventType, eventData, exceptions); + await PublishAsync(new LocalEventMessage(Guid.NewGuid(), eventData, eventType)); + } - if (exceptions.Any()) + public virtual async Task PublishAsync(LocalEventMessage localEventMessage) + { + await TriggerHandlersAsync(localEventMessage.EventType, localEventMessage.EventData, errorContext => { - if (exceptions.Count == 1) - { - exceptions[0].ReThrow(); - } - - throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); - } + errorContext.SetProperty("messageId", localEventMessage.MessageId); + }); } protected override IEnumerable GetHandlerFactories(Type eventType) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs new file mode 100644 index 0000000000..db8964d380 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.Local +{ + public class LocalEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + protected ILocalEventBus LocalEventBus { get; } + protected Dictionary RetryTracking { get; } + + public LocalEventErrorHandler( + IOptions options, + ILocalEventBus localEventBus) + : base(options) + { + LocalEventBus = localEventBus; + RetryTracking = new Dictionary(); + } + + protected override async Task Retry(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions.IntervalMillisecond > 0) + { + await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); + } + + var messageId = context.GetProperty("messageId"); + + await LocalEventBus.PublishAsync(context.EventType, + new LocalEventMessage(messageId, context.EventData, context.EventType)); + } + + protected override Task MoveToErrorQueue(EventExecutionErrorContext context) + { + if (context.Exceptions.Count == 1) + { + context.Exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + context.EventType, + context.Exceptions); + } + + protected override bool ShouldRetry(EventExecutionErrorContext context) + { + if (!base.ShouldRetry(context)) + { + return false; + } + + var messageId = context.GetProperty("messageId"); + + var index = RetryTracking.GetOrDefault(messageId); + + if (Options.RetryStrategyOptions.Count >= index) + { + RetryTracking.Remove(messageId); + return false; + } + + RetryTracking[messageId] = ++index; + + return true; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs new file mode 100644 index 0000000000..550de897bd --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs @@ -0,0 +1,20 @@ +using System; + +namespace Volo.Abp.EventBus.Local +{ + public class LocalEventMessage + { + public Guid MessageId { get; } + + public object EventData { get; } + + public Type EventType { get; } + + public LocalEventMessage(Guid messageId, object eventData, Type eventType) + { + MessageId = messageId; + EventData = eventData; + EventType = eventType; + } + } +} diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs index 87872b31a2..721f0b5a9b 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.Kafka public interface IKafkaMessageConsumer { void OnMessageReceived(Func, Task> callback); + + void Consume(); } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs index 58e718831c..a283eb7e50 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.Kafka byte[] Serialize(object obj); object Deserialize(byte[] value, Type type); + + T Deserialize(byte[] value); } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index 3b8f022012..f5c726063e 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -63,7 +63,6 @@ namespace Volo.Abp.Kafka GroupId = groupId; AsyncHelper.RunSync(CreateTopicAsync); - Consume(); } public virtual void OnMessageReceived(Func, Task> callback) @@ -98,7 +97,7 @@ namespace Volo.Abp.Kafka } } - protected virtual void Consume() + public virtual void Consume() { Consumer = ConsumerPool.Get(GroupId, ConnectionName); diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs index a04125f8a6..a8a199c140 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs @@ -23,5 +23,10 @@ namespace Volo.Abp.Kafka { return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); } + + public T Deserialize(byte[] value) + { + return _jsonSerializer.Deserialize(Encoding.UTF8.GetString(value)); + } } } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs index 771d1a05a6..2de5cd2191 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.RabbitMQ byte[] Serialize(object obj); object Deserialize(byte[] value, Type type); + + T Deserialize(byte[] value); } } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs index c179193fdd..cc815f686a 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs @@ -23,5 +23,10 @@ namespace Volo.Abp.RabbitMQ { return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); } + + public T Deserialize(byte[] value) + { + return _jsonSerializer.Deserialize(Encoding.UTF8.GetString(value)); + } } -} \ No newline at end of file +} From f6198eeb806b1fa2e5c382773c8a5fe629850b3d Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 23 Apr 2021 22:19:54 +0800 Subject: [PATCH 02/11] Implement EventErrorHandler for kafka --- .../Kafka/KafkaDistributedEventBus.cs | 21 ++++++++--- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 34 ++++-------------- .../Volo/Abp/EventBus/AbpEventBusOptions.cs | 2 +- .../Abp/EventBus/EventErrorHandlerBase.cs | 12 +++---- .../EventBus/Local/LocalEventErrorHandler.cs | 2 +- .../Volo/Abp/Kafka/IKafkaMessageConsumer.cs | 2 -- .../Abp/Kafka/IKafkaMessageConsumerFactory.cs | 2 ++ .../Volo/Abp/Kafka/KafkaMessageConsumer.cs | 36 ++++++++++++++----- .../Abp/Kafka/KafkaMessageConsumerFactory.cs | 1 + 9 files changed, 62 insertions(+), 50 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 46eb1c0827..b6dc3e9bb5 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -20,6 +20,7 @@ namespace Volo.Abp.EventBus.Kafka [ExposeServices(typeof(IDistributedEventBus), typeof(KafkaDistributedEventBus))] public class KafkaDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency { + protected AbpEventBusOptions AbpEventBusOptions { get; } protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; } @@ -28,6 +29,7 @@ namespace Volo.Abp.EventBus.Kafka protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } protected IKafkaMessageConsumer Consumer { get; private set; } + protected string DeadLetterTopicName { get; } public KafkaDistributedEventBus( IServiceScopeFactory serviceScopeFactory, @@ -37,14 +39,17 @@ namespace Volo.Abp.EventBus.Kafka IOptions abpDistributedEventBusOptions, IKafkaSerializer serializer, IProducerPool producerPool, - IEventErrorHandler errorHandler) + IEventErrorHandler errorHandler, + IOptions abpEventBusOptions) : base(serviceScopeFactory, currentTenant, errorHandler) { AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; + AbpEventBusOptions = abpEventBusOptions.Value; MessageConsumerFactory = messageConsumerFactory; Serializer = serializer; ProducerPool = producerPool; + DeadLetterTopicName = AbpEventBusOptions.DeadLetterQueue ?? AbpKafkaEventBusOptions.TopicName + "_error"; HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); @@ -56,8 +61,6 @@ namespace Volo.Abp.EventBus.Kafka AbpKafkaEventBusOptions.TopicName, AbpKafkaEventBusOptions.GroupId, AbpKafkaEventBusOptions.ConnectionName); - - Consumer.Consume(); Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); @@ -157,6 +160,16 @@ namespace Volo.Abp.EventBus.Kafka } public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers) + { + await PublishAsync(AbpKafkaEventBusOptions.TopicName, eventType, eventData, headers); + } + + public virtual async Task PublishToDeadLetterAsync(Type eventType, object eventData, Headers headers) + { + await PublishAsync(DeadLetterTopicName, eventType, eventData, headers); + } + + private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); @@ -164,7 +177,7 @@ namespace Volo.Abp.EventBus.Kafka var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); await producer.ProduceAsync( - AbpKafkaEventBusOptions.TopicName, + topicName, new Message { Key = eventName, Value = body, Headers = headers diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index 65c8561149..2ee2a23515 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -14,26 +14,14 @@ namespace Volo.Abp.EventBus.Kafka protected IKafkaSerializer Serializer { get; } protected KafkaDistributedEventBus EventBus { get; } - protected IProducerPool ProducerPool { get; } - protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } - - protected string ErrorTopicName { get; } public KafkaEventErrorHandler( IOptions options, IKafkaSerializer serializer, - KafkaDistributedEventBus eventBus, - IKafkaMessageConsumerFactory consumerFactory, - IProducerPool producerPool, - IOptions abpKafkaEventBusOptions) : base(options) + KafkaDistributedEventBus eventBus) : base(options) { Serializer = serializer; EventBus = eventBus; - ProducerPool = producerPool; - AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; - - ErrorTopicName = options.Value.ErrorQueue ?? abpKafkaEventBusOptions.Value.TopicName + "_error"; - consumerFactory.Create(ErrorTopicName, string.Empty, abpKafkaEventBusOptions.Value.ConnectionName); } protected override async Task Retry(EventExecutionErrorContext context) @@ -52,19 +40,12 @@ namespace Volo.Abp.EventBus.Kafka await EventBus.PublishAsync(context.EventType, context.EventData, headers); } - protected override async Task MoveToErrorQueue(EventExecutionErrorContext context) + protected override async Task MoveToDeadLetter(EventExecutionErrorContext context) { - var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); - var eventName = EventNameAttribute.GetNameOrDefault(context.EventType); - var body = Serializer.Serialize(context.EventData); - - await producer.ProduceAsync( - AbpKafkaEventBusOptions.TopicName, - new Message - { - Key = eventName, Value = body, - Headers = new Headers {{"exceptions", Serializer.Serialize(context.Exceptions)}} - }); + await EventBus.PublishToDeadLetterAsync(context.EventType, context.EventData, new Headers + { + {"exceptions", Serializer.Serialize(context.Exceptions)} + }); } protected override bool ShouldRetry(EventExecutionErrorContext context) @@ -75,14 +56,13 @@ namespace Volo.Abp.EventBus.Kafka } var headers = context.GetProperty(HeadersKey); - var index = 1; if (headers == null) { return true; } - index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + var index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); return Options.RetryStrategyOptions.Count < index; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs index 18d3290c25..ad3926b334 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.EventBus public Func ErrorHandleSelector { get; set; } - public string ErrorQueue { get; set; } + public string DeadLetterQueue { get; set; } public AbpEventBusRetryStrategyOptions RetryStrategyOptions { get; set; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs index 14a27c4221..0bb6430e56 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -1,7 +1,5 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Volo.Abp.Data; namespace Volo.Abp.EventBus { @@ -9,7 +7,7 @@ namespace Volo.Abp.EventBus { protected AbpEventBusOptions Options { get; } - public EventErrorHandlerBase(IOptions options) + protected EventErrorHandlerBase(IOptions options) { Options = options.Value; } @@ -27,12 +25,12 @@ namespace Volo.Abp.EventBus return; } - await MoveToErrorQueue(context); + await MoveToDeadLetter(context); } protected abstract Task Retry(EventExecutionErrorContext context); - protected abstract Task MoveToErrorQueue(EventExecutionErrorContext context); + protected abstract Task MoveToDeadLetter(EventExecutionErrorContext context); protected virtual bool ShouldHandle(EventExecutionErrorContext context) { @@ -51,7 +49,7 @@ namespace Volo.Abp.EventBus protected virtual bool ShouldRetry(EventExecutionErrorContext context) { - return Options.RetryStrategyOptions == null && false; + return Options.RetryStrategyOptions != null; } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs index db8964d380..5c3c2ba4f1 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -34,7 +34,7 @@ namespace Volo.Abp.EventBus.Local new LocalEventMessage(messageId, context.EventData, context.EventType)); } - protected override Task MoveToErrorQueue(EventExecutionErrorContext context) + protected override Task MoveToDeadLetter(EventExecutionErrorContext context) { if (context.Exceptions.Count == 1) { diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs index 721f0b5a9b..87872b31a2 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumer.cs @@ -7,7 +7,5 @@ namespace Volo.Abp.Kafka public interface IKafkaMessageConsumer { void OnMessageReceived(Func, Task> callback); - - void Consume(); } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs index 2b01b5a935..96ec753dc2 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs @@ -8,11 +8,13 @@ /// not disposed until end of the application. /// /// + /// /// /// /// IKafkaMessageConsumer Create( string topicName, + string deadLetterTopicName, string groupId, string connectionName = null); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index f5c726063e..e519ebc87d 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Confluent.Kafka; @@ -36,6 +37,9 @@ namespace Volo.Abp.Kafka protected string TopicName { get; private set; } + + protected string DeadLetterTopicName { get; private set; } + public KafkaMessageConsumer( IConsumerPool consumerPool, IExceptionNotifier exceptionNotifier, @@ -53,16 +57,20 @@ namespace Volo.Abp.Kafka public virtual void Initialize( [NotNull] string topicName, + [NotNull] string deadLetterTopicName, [NotNull] string groupId, string connectionName = null) { Check.NotNull(topicName, nameof(topicName)); + Check.NotNull(deadLetterTopicName, nameof(deadLetterTopicName)); Check.NotNull(groupId, nameof(groupId)); TopicName = topicName; + DeadLetterTopicName = deadLetterTopicName; ConnectionName = connectionName ?? KafkaConnections.DefaultConnectionName; GroupId = groupId; AsyncHelper.RunSync(CreateTopicAsync); + Consume(); } public virtual void OnMessageReceived(Func, Task> callback) @@ -74,22 +82,34 @@ namespace Volo.Abp.Kafka { using (var adminClient = new AdminClientBuilder(Options.Connections.GetOrDefault(ConnectionName)).Build()) { - var topic = new TopicSpecification + var topics = new List { - Name = TopicName, - NumPartitions = 1, - ReplicationFactor = 1 + new() + { + Name = TopicName, + NumPartitions = 1, + ReplicationFactor = 1 + }, + new() + { + Name = DeadLetterTopicName, + NumPartitions = 1, + ReplicationFactor = 1 + } }; - Options.ConfigureTopic?.Invoke(topic); + topics.ForEach(topic => + { + Options.ConfigureTopic?.Invoke(topic); + }); try { - await adminClient.CreateTopicsAsync(new[] {topic}); + await adminClient.CreateTopicsAsync(topics); } catch (CreateTopicsException e) { - if(e.Results.First().Error.Code != ErrorCode.TopicAlreadyExists) + if(e.Results.Any(x => x.Error.Code != ErrorCode.TopicAlreadyExists)) { throw; } @@ -97,7 +117,7 @@ namespace Volo.Abp.Kafka } } - public virtual void Consume() + protected virtual void Consume() { Consumer = ConsumerPool.Get(GroupId, ConnectionName); diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs index fb08aa4144..68d1162b7f 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs @@ -16,6 +16,7 @@ namespace Volo.Abp.Kafka public IKafkaMessageConsumer Create( string topicName, + string deadLetterTopicName, string groupId, string connectionName = null) { From 29e0cee1aa44ec2c525b728843be54e5eb6d4ac3 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 27 Apr 2021 15:08:29 +0800 Subject: [PATCH 03/11] Implement EventErrorHandler for rabbitmq & rebus --- .../Kafka/KafkaDistributedEventBus.cs | 2 +- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 2 +- .../RabbitMq/RabbitMqDistributedEventBus.cs | 43 +++++++++- .../RabbitMq/RabbitMqEventErrorHandler.cs | 82 +++++++++++++++++++ .../EventBus/Rebus/AbpEventBusRebusModule.cs | 26 +++--- .../Rebus/AbpEventBusRebusOptionsSetup.cs | 12 +++ .../EventBus/Rebus/AbpRebusEventBusOptions.cs | 8 +- .../EventBus/Rebus/RebusEventErrorHandler.cs | 44 ++++++++++ .../Volo/Abp/EventBus/AbpEventBusModule.cs | 8 ++ .../Volo/Abp/EventBus/AbpEventBusOptions.cs | 2 +- .../AbpEventBusRetryStrategyOptions.cs | 3 +- .../EventBus/Local/LocalEventErrorHandler.cs | 2 +- .../Volo/Abp/Kafka/AbpKafkaOptions.cs | 2 - .../Volo/Abp/Kafka/KafkaMessageConsumer.cs | 36 ++++---- .../Abp/RabbitMQ/QueueDeclareConfiguration.cs | 16 ++-- .../Abp/RabbitMQ/RabbitMqMessageConsumer.cs | 26 ++++-- 16 files changed, 258 insertions(+), 56 deletions(-) create mode 100644 framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs create mode 100644 framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs create mode 100644 framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index b6dc3e9bb5..aeeccef062 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -49,7 +49,7 @@ namespace Volo.Abp.EventBus.Kafka MessageConsumerFactory = messageConsumerFactory; Serializer = serializer; ProducerPool = producerPool; - DeadLetterTopicName = AbpEventBusOptions.DeadLetterQueue ?? AbpKafkaEventBusOptions.TopicName + "_error"; + DeadLetterTopicName = AbpEventBusOptions.DeadLetterName ?? AbpKafkaEventBusOptions.TopicName + "_dead_letter"; HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index 2ee2a23515..b9e46fdd6a 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -64,7 +64,7 @@ namespace Volo.Abp.EventBus.Kafka var index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); - return Options.RetryStrategyOptions.Count < index; + return Options.RetryStrategyOptions.MaxRetryAttempts < index; } } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 5baeda6d9f..aa1aacc485 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -25,6 +25,7 @@ namespace Volo.Abp.EventBus.RabbitMq { protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + protected AbpEventBusOptions AbpEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } protected IRabbitMqSerializer Serializer { get; } @@ -42,12 +43,14 @@ namespace Volo.Abp.EventBus.RabbitMq IOptions distributedEventBusOptions, IRabbitMqMessageConsumerFactory messageConsumerFactory, ICurrentTenant currentTenant, - IEventErrorHandler errorHandler) + IEventErrorHandler errorHandler, + IOptions abpEventBusOptions) : base(serviceScopeFactory, currentTenant, errorHandler) { ConnectionPool = connectionPool; Serializer = serializer; MessageConsumerFactory = messageConsumerFactory; + AbpEventBusOptions = abpEventBusOptions.Value; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpRabbitMqEventBusOptions = options.Value; @@ -57,6 +60,8 @@ namespace Volo.Abp.EventBus.RabbitMq public void Initialize() { + const string suffix = "_dead_letter"; + Consumer = MessageConsumerFactory.Create( new ExchangeDeclareConfiguration( AbpRabbitMqEventBusOptions.ExchangeName, @@ -67,7 +72,12 @@ namespace Volo.Abp.EventBus.RabbitMq AbpRabbitMqEventBusOptions.ClientName, durable: true, exclusive: false, - autoDelete: false + autoDelete: false, + arguments: new Dictionary + { + {"x-dead-letter-exchange", AbpRabbitMqEventBusOptions.ExchangeName + suffix}, + {"x-dead-letter-routing-key", AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix} + } ), AbpRabbitMqEventBusOptions.ConnectionName ); @@ -197,6 +207,35 @@ namespace Volo.Abp.EventBus.RabbitMq return Task.CompletedTask; } + public Task PublishAsync(Type eventType, object eventData, Dictionary headers) + { + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + var body = Serializer.Serialize(eventData); + + using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) + { + channel.ExchangeDeclare( + AbpRabbitMqEventBusOptions.ExchangeName, + "direct", + durable: true + ); + + var properties = channel.CreateBasicProperties(); + properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; + properties.Headers = headers; + + channel.BasicPublish( + exchange: AbpRabbitMqEventBusOptions.ExchangeName, + routingKey: eventName, + mandatory: true, + basicProperties: properties, + body: body + ); + } + + return Task.CompletedTask; + } + private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs new file mode 100644 index 0000000000..a17bcfcdcc --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.RabbitMq +{ + public class RabbitMqEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + public const string HeadersKey = "headers"; + public const string RetryIndexKey = "retryIndex"; + + protected RabbitMqDistributedEventBus EventBus { get; } + + public RabbitMqEventErrorHandler( + IOptions options, + RabbitMqDistributedEventBus eventBus) + : base(options) + { + EventBus = eventBus; + } + + protected override async Task Retry(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions.IntervalMillisecond > 0) + { + await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); + } + + var headers = context.GetProperty>(HeadersKey) ?? + new Dictionary(); + + var index = 1; + if (headers.ContainsKey(RetryIndexKey)) + { + index = (int) headers[RetryIndexKey]; + headers[RetryIndexKey] = ++index; + } + else + { + headers[RetryIndexKey] = index; + } + + headers["exceptions"] = context.Exceptions; + + await EventBus.PublishAsync(context.EventType, context.EventData, headers); + } + + protected override Task MoveToDeadLetter(EventExecutionErrorContext context) + { + if (context.Exceptions.Count == 1) + { + context.Exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + context.EventType, + context.Exceptions); + } + + protected override bool ShouldRetry(EventExecutionErrorContext context) + { + if (!base.ShouldRetry(context)) + { + return false; + } + + var headers = context.GetProperty>(HeadersKey); + + if (headers == null || !headers.ContainsKey(RetryIndexKey)) + { + return true; + } + + var index = (int) headers[RetryIndexKey]; + + return Options.RetryStrategyOptions.MaxRetryAttempts < index; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs index 9ebd91a6a5..83b3f84340 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Rebus.Handlers; +using Rebus.Retry.Simple; using Rebus.ServiceProvider; using Volo.Abp.Modularity; @@ -11,23 +12,28 @@ namespace Volo.Abp.EventBus.Rebus { public override void ConfigureServices(ServiceConfigurationContext context) { - var options = context.Services.ExecutePreConfiguredActions(); + var abpEventBusOptions = context.Services.ExecutePreConfiguredActions(); context.Services.AddTransient(typeof(IHandleMessages<>), typeof(RebusDistributedEventHandlerAdapter<>)); Configure(rebusOptions => { - rebusOptions.Configurer = options.Configurer; - rebusOptions.Publish = options.Publish; - rebusOptions.InputQueueName = options.InputQueueName; - }); + context.Services.ExecutePreConfiguredActions(rebusOptions); - context.Services.AddRebus(configurer => - { - options.Configurer?.Invoke(configurer); - return configurer; - }); + context.Services.AddRebus(configure => + { + if (abpEventBusOptions.RetryStrategyOptions != null) + { + configure.Options(b => + b.SimpleRetryStrategy( + errorQueueAddress: abpEventBusOptions.DeadLetterName ?? rebusOptions.InputQueueName + "_dead_letter", + maxDeliveryAttempts: abpEventBusOptions.RetryStrategyOptions.MaxRetryAttempts)); + } + rebusOptions.Configurer?.Invoke(configure); + return configure; + }); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs new file mode 100644 index 0000000000..7d26f174fe --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Options; + +namespace Volo.Abp.EventBus.Rebus +{ + public class AbpEventBusRebusOptionsSetup : IConfigureOptions + { + public void Configure(AbpEventBusOptions options) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs index 8aaee7bd63..b6343204c0 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.EventBus.Rebus public AbpRebusEventBusOptions() { _publish = DefaultPublish; - _configurer = DefaultConfigurer; + _configurer = DefaultConfigure; } private async Task DefaultPublish(IBus bus, Type eventType, object eventData) @@ -40,10 +40,10 @@ namespace Volo.Abp.EventBus.Rebus await bus.Advanced.Routing.Send(InputQueueName, eventData); } - private void DefaultConfigurer(RebusConfigurer configurer) + private void DefaultConfigure(RebusConfigurer configure) { - configurer.Subscriptions(s => s.StoreInMemory()); - configurer.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName)); + configure.Subscriptions(s => s.StoreInMemory()); + configure.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName)); } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs new file mode 100644 index 0000000000..c75715ebf8 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.Rebus +{ + public class RebusEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + public RebusEventErrorHandler( + IOptions options) + : base(options) + { + } + + protected override Task Retry(EventExecutionErrorContext context) + { + Throw(context); + + return Task.CompletedTask; + } + + protected override Task MoveToDeadLetter(EventExecutionErrorContext context) + { + Throw(context); + + return Task.CompletedTask; + } + + private void Throw(EventExecutionErrorContext context) + { + // Rebus will automatic retries and error handling: https://github.com/rebus-org/Rebus/wiki/Automatic-retries-and-error-handling + + if (context.Exceptions.Count == 1) + { + context.Exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + context.EventType, + context.Exceptions); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index eb9c17b129..014308c8e6 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -20,6 +20,14 @@ namespace Volo.Abp.EventBus AddEventHandlers(context.Services); } + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + context.Services.ExecutePreConfiguredActions(options); + }); + } + private static void AddEventHandlers(IServiceCollection services) { var localHandlers = new List(); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs index ad3926b334..3ba5c2a548 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.EventBus public Func ErrorHandleSelector { get; set; } - public string DeadLetterQueue { get; set; } + public string DeadLetterName { get; set; } public AbpEventBusRetryStrategyOptions RetryStrategyOptions { get; set; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs index 5762e8d7a0..4b5b722e96 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs @@ -2,9 +2,8 @@ { public class AbpEventBusRetryStrategyOptions { - public int IntervalMillisecond { get; set; } = 3000; - public int Count { get; set; } = 3; + public int MaxRetryAttempts { get; set; } = 3; } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs index 5c3c2ba4f1..da7a0d7f71 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -57,7 +57,7 @@ namespace Volo.Abp.EventBus.Local var index = RetryTracking.GetOrDefault(messageId); - if (Options.RetryStrategyOptions.Count >= index) + if (Options.RetryStrategyOptions.MaxRetryAttempts >= index) { RetryTracking.Remove(messageId); return false; diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs index 26d15ce818..f6679aec3c 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs @@ -14,8 +14,6 @@ namespace Volo.Abp.Kafka public Action ConfigureTopic { get; set; } - public bool ReQueue { get; set; } = true; - public AbpKafkaOptions() { Connections = new KafkaConnections(); diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index e519ebc87d..279765efdb 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -27,6 +27,8 @@ namespace Volo.Abp.Kafka protected AbpKafkaOptions Options { get; } + protected AbpAsyncTimer Timer { get; } + protected ConcurrentBag, Task>> Callbacks { get; } protected IConsumer Consumer { get; private set; } @@ -37,22 +39,27 @@ namespace Volo.Abp.Kafka protected string TopicName { get; private set; } - protected string DeadLetterTopicName { get; private set; } public KafkaMessageConsumer( IConsumerPool consumerPool, IExceptionNotifier exceptionNotifier, IOptions options, - IProducerPool producerPool) + IProducerPool producerPool, + AbpAsyncTimer timer) { ConsumerPool = consumerPool; ExceptionNotifier = exceptionNotifier; ProducerPool = producerPool; + Timer = timer; Options = options.Value; Logger = NullLogger.Instance; Callbacks = new ConcurrentBag, Task>>(); + + Timer.Period = 5000; //5 sec. + Timer.Elapsed = Timer_Elapsed; + Timer.RunOnStart = true; } public virtual void Initialize( @@ -68,9 +75,7 @@ namespace Volo.Abp.Kafka DeadLetterTopicName = deadLetterTopicName; ConnectionName = connectionName ?? KafkaConnections.DefaultConnectionName; GroupId = groupId; - - AsyncHelper.RunSync(CreateTopicAsync); - Consume(); + Timer.Start(); } public virtual void OnMessageReceived(Func, Task> callback) @@ -78,6 +83,14 @@ namespace Volo.Abp.Kafka Callbacks.Add(callback); } + protected virtual async Task Timer_Elapsed(AbpAsyncTimer timer) + { + await CreateTopicAsync(); + Consume(); + + Timer.Stop(); + } + protected virtual async Task CreateTopicAsync() { using (var adminClient = new AdminClientBuilder(Options.Connections.GetOrDefault(ConnectionName)).Build()) @@ -158,8 +171,6 @@ namespace Volo.Abp.Kafka } catch (Exception ex) { - await RequeueAsync(consumeResult); - Logger.LogException(ex); await ExceptionNotifier.NotifyAsync(ex); } @@ -169,17 +180,6 @@ namespace Volo.Abp.Kafka } } - protected virtual async Task RequeueAsync(ConsumeResult consumeResult) - { - if (!Options.ReQueue) - { - return; - } - - var producer = ProducerPool.Get(ConnectionName); - await producer.ProduceAsync(consumeResult.Topic, consumeResult.Message); - } - public virtual void Dispose() { if (Consumer == null) diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs index 8cc07b7bb9..17a5c2dfbf 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs @@ -6,8 +6,7 @@ namespace Volo.Abp.RabbitMQ { public class QueueDeclareConfiguration { - [NotNull] - public string QueueName { get; } + [NotNull] public string QueueName { get; } public bool Durable { get; set; } @@ -18,16 +17,17 @@ namespace Volo.Abp.RabbitMQ public IDictionary Arguments { get; } public QueueDeclareConfiguration( - [NotNull] string queueName, - bool durable = true, - bool exclusive = false, - bool autoDelete = false) + [NotNull] string queueName, + bool durable = true, + bool exclusive = false, + bool autoDelete = false, + Dictionary arguments = null) { QueueName = queueName; Durable = durable; Exclusive = exclusive; AutoDelete = autoDelete; - Arguments = new Dictionary(); + Arguments = arguments ?? new Dictionary(); } public virtual QueueDeclareOk Declare(IModel channel) @@ -41,4 +41,4 @@ namespace Volo.Abp.RabbitMQ ); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 894f68d097..b5a165157f 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -147,6 +147,7 @@ namespace Volo.Abp.RabbitMQ Channel = ConnectionPool .Get(ConnectionName) .CreateModel(); + Channel.ExchangeDeclare( exchange: Exchange.ExchangeName, type: Exchange.Type, @@ -155,6 +156,23 @@ namespace Volo.Abp.RabbitMQ arguments: Exchange.Arguments ); + if (Queue.Arguments.ContainsKey("x-dead-letter-exchange") && + Queue.Arguments.ContainsKey("x-dead-letter-routing-key")) + { + Channel.ExchangeDeclare( + Exchange.Arguments["x-dead-letter-exchange"].ToString(), + Exchange.Type, + Exchange.Durable, + Exchange.AutoDelete + ); + + Channel.QueueDeclare( + Queue.Arguments["x-dead-letter-routing-key"].ToString(), + Queue.Durable, + Queue.Exclusive, + Queue.AutoDelete); + } + Channel.QueueDeclare( queue: Queue.QueueName, durable: Queue.Durable, @@ -194,14 +212,10 @@ namespace Volo.Abp.RabbitMQ { try { - Channel.BasicNack( - basicDeliverEventArgs.DeliveryTag, - multiple: false, - requeue: true - ); + Channel.BasicReject(basicDeliverEventArgs.DeliveryTag, false); } catch { } - + Logger.LogException(ex); await ExceptionNotifier.NotifyAsync(ex); } From 3d2348134e2890b71e234bc5333141a044ad57ad Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 27 Apr 2021 16:40:44 +0800 Subject: [PATCH 04/11] Add unit tests --- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 22 +++--- .../RabbitMq/RabbitMqEventErrorHandler.cs | 10 +-- .../Volo/Abp/EventBus/AbpEventBusOptions.cs | 1 + .../Volo/Abp/EventBus/EventBusBase.cs | 7 +- .../Abp/EventBus/EventErrorHandlerBase.cs | 12 +++- .../EventBus/EventExecutionErrorContext.cs | 6 +- .../EventBus/Local/LocalEventErrorHandler.cs | 12 ++-- .../Volo/Abp/EventBus/EventBusTestModule.cs | 13 +++- .../Local/EventBus_Exception_Handler_Tests.cs | 72 +++++++++++++++++++ .../EventBus/MyExceptionHandleEventData.cs | 12 ++++ 10 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs create mode 100644 framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index b9e46fdd6a..567c303704 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; using Confluent.Kafka; using Microsoft.Extensions.Options; using Volo.Abp.Data; @@ -13,15 +15,12 @@ namespace Volo.Abp.EventBus.Kafka public const string RetryIndexKey = "retryIndex"; protected IKafkaSerializer Serializer { get; } - protected KafkaDistributedEventBus EventBus { get; } public KafkaEventErrorHandler( IOptions options, - IKafkaSerializer serializer, - KafkaDistributedEventBus eventBus) : base(options) + IKafkaSerializer serializer) : base(options) { Serializer = serializer; - EventBus = eventBus; } protected override async Task Retry(EventExecutionErrorContext context) @@ -32,17 +31,22 @@ namespace Volo.Abp.EventBus.Kafka } var headers = context.GetProperty(HeadersKey) ?? new Headers(); - var index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + + var index = 1; + if (headers.Any(x => x.Key == RetryIndexKey)) + { + index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + } headers.Remove(RetryIndexKey); headers.Add(RetryIndexKey, Serializer.Serialize(++index)); - await EventBus.PublishAsync(context.EventType, context.EventData, headers); + await context.EventBus.As().PublishAsync(context.EventType, context.EventData, headers); } protected override async Task MoveToDeadLetter(EventExecutionErrorContext context) { - await EventBus.PublishToDeadLetterAsync(context.EventType, context.EventData, new Headers + await context.EventBus.As().PublishToDeadLetterAsync(context.EventType, context.EventData, new Headers { {"exceptions", Serializer.Serialize(context.Exceptions)} }); @@ -64,7 +68,7 @@ namespace Volo.Abp.EventBus.Kafka var index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); - return Options.RetryStrategyOptions.MaxRetryAttempts < index; + return Options.RetryStrategyOptions.MaxRetryAttempts > index; } } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs index a17bcfcdcc..b9b0ac6497 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -12,14 +12,10 @@ namespace Volo.Abp.EventBus.RabbitMq public const string HeadersKey = "headers"; public const string RetryIndexKey = "retryIndex"; - protected RabbitMqDistributedEventBus EventBus { get; } - public RabbitMqEventErrorHandler( - IOptions options, - RabbitMqDistributedEventBus eventBus) + IOptions options) : base(options) { - EventBus = eventBus; } protected override async Task Retry(EventExecutionErrorContext context) @@ -45,7 +41,7 @@ namespace Volo.Abp.EventBus.RabbitMq headers["exceptions"] = context.Exceptions; - await EventBus.PublishAsync(context.EventType, context.EventData, headers); + await context.EventBus.As().PublishAsync(context.EventType, context.EventData, headers); } protected override Task MoveToDeadLetter(EventExecutionErrorContext context) @@ -76,7 +72,7 @@ namespace Volo.Abp.EventBus.RabbitMq var index = (int) headers[RetryIndexKey]; - return Options.RetryStrategyOptions.MaxRetryAttempts < index; + return Options.RetryStrategyOptions.MaxRetryAttempts > index; } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs index 3ba5c2a548..39631c7e18 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs @@ -14,6 +14,7 @@ namespace Volo.Abp.EventBus public void UseRetryStrategy(Action action = null) { + EnabledErrorHandle = true; RetryStrategyOptions = new AbpEventBusRetryStrategyOptions(); action?.Invoke(RetryStrategyOptions); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index e505f30ccb..6dcaa496b4 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -104,7 +104,7 @@ namespace Volo.Abp.EventBus if (exceptions.Any()) { - var context = new EventExecutionErrorContext(exceptions, eventData, eventType); + var context = new EventExecutionErrorContext(exceptions, eventData, eventType, this); onErrorAction?.Invoke(context); await ErrorHandler.Handle(context); } @@ -221,11 +221,6 @@ namespace Volo.Abp.EventBus }; } - protected virtual void OnErrorHandle(EventExecutionErrorContext context) - { - - } - protected class EventTypeWithEventHandlerFactories { public Type EventType { get; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs index 0bb6430e56..b5d22389d3 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.Extensions.Options; namespace Volo.Abp.EventBus @@ -16,7 +17,14 @@ namespace Volo.Abp.EventBus { if (!ShouldHandle(context)) { - return; + if (context.Exceptions.Count == 1) + { + context.Exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + context.EventType, + context.Exceptions); } if (ShouldRetry(context)) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs index cf40102579..29c362c1ca 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Volo.Abp.EventBus.Local; using Volo.Abp.ObjectExtending; namespace Volo.Abp.EventBus @@ -13,11 +12,14 @@ namespace Volo.Abp.EventBus public Type EventType { get; } - public EventExecutionErrorContext(List exceptions, object eventData, Type eventType) + public IEventBus EventBus { get; } + + public EventExecutionErrorContext(List exceptions, object eventData, Type eventType, IEventBus eventBus) { Exceptions = exceptions; EventData = eventData; EventType = eventType; + EventBus = eventBus; } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs index da7a0d7f71..79fd405940 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -9,15 +9,12 @@ namespace Volo.Abp.EventBus.Local { public class LocalEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { - protected ILocalEventBus LocalEventBus { get; } protected Dictionary RetryTracking { get; } public LocalEventErrorHandler( - IOptions options, - ILocalEventBus localEventBus) + IOptions options) : base(options) { - LocalEventBus = localEventBus; RetryTracking = new Dictionary(); } @@ -30,8 +27,9 @@ namespace Volo.Abp.EventBus.Local var messageId = context.GetProperty("messageId"); - await LocalEventBus.PublishAsync(context.EventType, - new LocalEventMessage(messageId, context.EventData, context.EventType)); + await context.EventBus.As().PublishAsync(new LocalEventMessage(messageId, context.EventData, context.EventType)); + + RetryTracking.Remove(messageId); } protected override Task MoveToDeadLetter(EventExecutionErrorContext context) @@ -57,7 +55,7 @@ namespace Volo.Abp.EventBus.Local var index = RetryTracking.GetOrDefault(messageId); - if (Options.RetryStrategyOptions.MaxRetryAttempts >= index) + if (Options.RetryStrategyOptions.MaxRetryAttempts <= index) { RetryTracking.Remove(messageId); return false; diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/EventBusTestModule.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/EventBusTestModule.cs index f514f37018..f260fecbea 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/EventBusTestModule.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/EventBusTestModule.cs @@ -5,6 +5,17 @@ namespace Volo.Abp.EventBus [DependsOn(typeof(AbpEventBusModule))] public class EventBusTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.UseRetryStrategy(retryStrategyOptions => + { + retryStrategyOptions.IntervalMillisecond = 0; + }); + options.ErrorHandleSelector = type => type == typeof(MyExceptionHandleEventData); + }); + } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs new file mode 100644 index 0000000000..7482606416 --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.EventBus.Local +{ + public class EventBus_Exception_Handler_Tests : EventBusTestBase + { + [Fact] + public async Task Should_Not_Handle_Exception() + { + MySimpleEventData data = null; + LocalEventBus.Subscribe(eventData => + { + ++eventData.Value; + data = eventData; + throw new Exception("This exception is intentionally thrown!"); + }); + + var appException = await Assert.ThrowsAsync(async () => + { + await LocalEventBus.PublishAsync(new MySimpleEventData(1)); + }); + + data.Value.ShouldBe(2); + appException.Message.ShouldBe("This exception is intentionally thrown!"); + } + + [Fact] + public async Task Should_Handle_Exception() + { + MyExceptionHandleEventData data = null; + LocalEventBus.Subscribe(eventData => + { + ++eventData.RetryAttempts; + data = eventData; + + if (eventData.RetryAttempts < 2) + { + throw new Exception("This exception is intentionally thrown!"); + } + + return Task.CompletedTask; + + }); + + await LocalEventBus.PublishAsync(new MyExceptionHandleEventData(0)); + data.RetryAttempts.ShouldBe(2); + } + + [Fact] + public async Task Should_Throw_Exception_After_Error_Handle() + { + MyExceptionHandleEventData data = null; + LocalEventBus.Subscribe(eventData => + { + ++eventData.RetryAttempts; + data = eventData; + throw new Exception("This exception is intentionally thrown!"); + }); + + var appException = await Assert.ThrowsAsync(async () => + { + await LocalEventBus.PublishAsync(new MyExceptionHandleEventData(0)); + }); + + data.RetryAttempts.ShouldBe(4); + appException.Message.ShouldBe("This exception is intentionally thrown!"); + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs new file mode 100644 index 0000000000..b68f60aecd --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.EventBus +{ + public class MyExceptionHandleEventData + { + public int RetryAttempts { get; set; } + + public MyExceptionHandleEventData(int retryAttempts) + { + RetryAttempts = retryAttempts; + } + } +} From fbedc63bc314b5fe7018047ce61be4ce945ea226 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 27 Apr 2021 19:10:52 +0800 Subject: [PATCH 05/11] Improved --- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 2 +- .../RabbitMq/RabbitMqDistributedEventBus.cs | 59 +++++++------------ .../RabbitMq/RabbitMqEventErrorHandler.cs | 25 ++++---- .../Abp/EventBus/EventErrorHandlerBase.cs | 2 +- .../RabbitMQ/ExchangeDeclareConfiguration.cs | 12 ++-- .../Abp/RabbitMQ/QueueDeclareConfiguration.cs | 4 ++ .../Abp/RabbitMQ/RabbitMqMessageConsumer.cs | 13 ++-- 7 files changed, 56 insertions(+), 61 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index 567c303704..6bb1307a12 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.EventBus.Kafka var headers = context.GetProperty(HeadersKey) ?? new Headers(); - var index = 1; + var index = 0; if (headers.Any(x => x.Key == RetryIndexKey)) { index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index aa1aacc485..ad672b8b7d 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using RabbitMQ.Client; using RabbitMQ.Client.Events; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.MultiTenancy; @@ -65,19 +66,16 @@ namespace Volo.Abp.EventBus.RabbitMq Consumer = MessageConsumerFactory.Create( new ExchangeDeclareConfiguration( AbpRabbitMqEventBusOptions.ExchangeName, + AbpRabbitMqEventBusOptions.ExchangeName + suffix, type: "direct", durable: true ), new QueueDeclareConfiguration( AbpRabbitMqEventBusOptions.ClientName, + AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix, durable: true, exclusive: false, - autoDelete: false, - arguments: new Dictionary - { - {"x-dead-letter-exchange", AbpRabbitMqEventBusOptions.ExchangeName + suffix}, - {"x-dead-letter-routing-key", AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix} - } + autoDelete: false ), AbpRabbitMqEventBusOptions.ConnectionName ); @@ -98,7 +96,10 @@ namespace Volo.Abp.EventBus.RabbitMq var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); - await TriggerHandlersAsync(eventType, eventData); + await TriggerHandlersAsync(eventType, eventData, errorContext => + { + errorContext.SetProperty("headers", ea.BasicProperties); + }); } public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class @@ -179,35 +180,12 @@ namespace Volo.Abp.EventBus.RabbitMq GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - public override Task PublishAsync(Type eventType, object eventData) + public override async Task PublishAsync(Type eventType, object eventData) { - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - var body = Serializer.Serialize(eventData); - - using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) - { - channel.ExchangeDeclare( - AbpRabbitMqEventBusOptions.ExchangeName, - "direct", - durable: true - ); - - var properties = channel.CreateBasicProperties(); - properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; - - channel.BasicPublish( - exchange: AbpRabbitMqEventBusOptions.ExchangeName, - routingKey: eventName, - mandatory: true, - basicProperties: properties, - body: body - ); - } - - return Task.CompletedTask; + await PublishAsync(eventType, eventData, null); } - public Task PublishAsync(Type eventType, object eventData, Dictionary headers) + public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); @@ -220,9 +198,12 @@ namespace Volo.Abp.EventBus.RabbitMq durable: true ); - var properties = channel.CreateBasicProperties(); - properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; - properties.Headers = headers; + if (properties == null) + { + properties = channel.CreateBasicProperties(); + properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; + properties.MessageId = Guid.NewGuid().ToString("N"); + } channel.BasicPublish( exchange: AbpRabbitMqEventBusOptions.ExchangeName, @@ -253,9 +234,11 @@ namespace Volo.Abp.EventBus.RabbitMq { var handlerFactoryList = new List(); - foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) + foreach (var handlerFactory in + HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) { - handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + handlerFactoryList.Add( + new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); } return handlerFactoryList.ToArray(); diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs index b9b0ac6497..01ec6a3888 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using RabbitMQ.Client; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -25,23 +27,20 @@ namespace Volo.Abp.EventBus.RabbitMq await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); } - var headers = context.GetProperty>(HeadersKey) ?? - new Dictionary(); + var properties = context.GetProperty(HeadersKey).As(); + var headers = properties.Headers ?? new Dictionary(); - var index = 1; + var index = 0; if (headers.ContainsKey(RetryIndexKey)) { index = (int) headers[RetryIndexKey]; - headers[RetryIndexKey] = ++index; - } - else - { - headers[RetryIndexKey] = index; } - headers["exceptions"] = context.Exceptions; + headers[RetryIndexKey] = ++index; + headers["exceptions"] = context.Exceptions.Select(x => x.ToString()).ToList(); + properties.Headers = headers; - await context.EventBus.As().PublishAsync(context.EventType, context.EventData, headers); + await context.EventBus.As().PublishAsync(context.EventType, context.EventData, properties); } protected override Task MoveToDeadLetter(EventExecutionErrorContext context) @@ -63,14 +62,14 @@ namespace Volo.Abp.EventBus.RabbitMq return false; } - var headers = context.GetProperty>(HeadersKey); + var properties = context.GetProperty(HeadersKey).As(); - if (headers == null || !headers.ContainsKey(RetryIndexKey)) + if (properties.Headers == null || !properties.Headers.ContainsKey(RetryIndexKey)) { return true; } - var index = (int) headers[RetryIndexKey]; + var index = (int) properties.Headers[RetryIndexKey]; return Options.RetryStrategyOptions.MaxRetryAttempts > index; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs index b5d22389d3..32240eb0e5 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -52,7 +52,7 @@ namespace Volo.Abp.EventBus return Options.ErrorHandleSelector.Invoke(context.EventType); } - return false; + return true; } protected virtual bool ShouldRetry(EventExecutionErrorContext context) diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs index 67f234aec4..18af1b758e 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs @@ -6,6 +6,8 @@ namespace Volo.Abp.RabbitMQ { public string ExchangeName { get; } + public string DeadLetterExchangeName { get; } + public string Type { get; } public bool Durable { get; set; } @@ -15,16 +17,18 @@ namespace Volo.Abp.RabbitMQ public IDictionary Arguments { get; } public ExchangeDeclareConfiguration( - string exchangeName, - string type, - bool durable = false, + string exchangeName, + string deadLetterExchangeName, + string type, + bool durable = false, bool autoDelete = false) { ExchangeName = exchangeName; + DeadLetterExchangeName = deadLetterExchangeName; Type = type; Durable = durable; AutoDelete = autoDelete; Arguments = new Dictionary(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs index 17a5c2dfbf..4fc0aff1ef 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.RabbitMQ { [NotNull] public string QueueName { get; } + [NotNull] public string DeadLetterQueueName { get; } + public bool Durable { get; set; } public bool Exclusive { get; set; } @@ -18,12 +20,14 @@ namespace Volo.Abp.RabbitMQ public QueueDeclareConfiguration( [NotNull] string queueName, + [NotNull] string deadLetterQueueName, bool durable = true, bool exclusive = false, bool autoDelete = false, Dictionary arguments = null) { QueueName = queueName; + DeadLetterQueueName = deadLetterQueueName; Durable = durable; Exclusive = exclusive; AutoDelete = autoDelete; diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index b5a165157f..100a79cc11 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -156,21 +156,26 @@ namespace Volo.Abp.RabbitMQ arguments: Exchange.Arguments ); - if (Queue.Arguments.ContainsKey("x-dead-letter-exchange") && - Queue.Arguments.ContainsKey("x-dead-letter-routing-key")) + if (!Exchange.DeadLetterExchangeName.IsNullOrWhiteSpace() && + !Queue.DeadLetterQueueName.IsNullOrWhiteSpace()) { Channel.ExchangeDeclare( - Exchange.Arguments["x-dead-letter-exchange"].ToString(), + Exchange.DeadLetterExchangeName, Exchange.Type, Exchange.Durable, Exchange.AutoDelete ); Channel.QueueDeclare( - Queue.Arguments["x-dead-letter-routing-key"].ToString(), + Queue.DeadLetterQueueName, Queue.Durable, Queue.Exclusive, Queue.AutoDelete); + + Queue.Arguments["x-dead-letter-exchange"] = Exchange.DeadLetterExchangeName; + Queue.Arguments["x-dead-letter-routing-key"] = Queue.DeadLetterQueueName; + + Channel.QueueBind(Queue.DeadLetterQueueName, Exchange.DeadLetterExchangeName, Queue.DeadLetterQueueName); } Channel.QueueDeclare( From 1ecc16d161eff4100509b1ebb89349b1671df818 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 28 Apr 2021 16:22:02 +0800 Subject: [PATCH 06/11] Improved --- .../Kafka/KafkaDistributedEventBus.cs | 9 +++--- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 29 +++++++++---------- .../RabbitMq/RabbitMqEventErrorHandler.cs | 16 +++++----- .../EventBus/Rebus/AbpEventBusRebusModule.cs | 23 ++++++++------- .../Volo/Abp/Kafka/KafkaMessageConsumer.cs | 1 - .../Abp/Kafka/KafkaMessageConsumerFactory.cs | 2 +- .../RabbitMQ/ExchangeDeclareConfiguration.cs | 2 +- .../Abp/RabbitMQ/QueueDeclareConfiguration.cs | 4 +-- .../Abp/RabbitMQ/RabbitMqMessageConsumer.cs | 14 ++++++++- 9 files changed, 56 insertions(+), 44 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index aeeccef062..0ca0eaebbd 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; -using Volo.Abp.EventBus.Local; using Volo.Abp.Kafka; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -49,7 +48,8 @@ namespace Volo.Abp.EventBus.Kafka MessageConsumerFactory = messageConsumerFactory; Serializer = serializer; ProducerPool = producerPool; - DeadLetterTopicName = AbpEventBusOptions.DeadLetterName ?? AbpKafkaEventBusOptions.TopicName + "_dead_letter"; + DeadLetterTopicName = + AbpEventBusOptions.DeadLetterName ?? AbpKafkaEventBusOptions.TopicName + "_dead_letter"; HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); @@ -59,6 +59,7 @@ namespace Volo.Abp.EventBus.Kafka { Consumer = MessageConsumerFactory.Create( AbpKafkaEventBusOptions.TopicName, + DeadLetterTopicName, AbpKafkaEventBusOptions.GroupId, AbpKafkaEventBusOptions.ConnectionName); Consumer.OnMessageReceived(ProcessEventAsync); @@ -75,7 +76,7 @@ namespace Volo.Abp.EventBus.Kafka return; } - var eventMessage = Serializer.Deserialize(message.Value); + var eventMessage = Serializer.Deserialize(message.Value, eventType); await TriggerHandlersAsync(eventType, eventMessage, context => { context.SetProperty(KafkaEventErrorHandler.HeadersKey, message.Headers); }); @@ -156,7 +157,7 @@ namespace Volo.Abp.EventBus.Kafka public override async Task PublishAsync(Type eventType, object eventData) { - await PublishAsync(eventType, eventData, null); + await PublishAsync(eventType, eventData, new Headers {{"messageId", Serializer.Serialize(Guid.NewGuid())}}); } public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index 6bb1307a12..d5e4452618 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.EventBus.Kafka public class KafkaEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { public const string HeadersKey = "headers"; - public const string RetryIndexKey = "retryIndex"; + public const string RetryAttemptKey = "retryAttempt"; protected IKafkaSerializer Serializer { get; } @@ -30,26 +30,25 @@ namespace Volo.Abp.EventBus.Kafka await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); } - var headers = context.GetProperty(HeadersKey) ?? new Headers(); + var headers = context.GetProperty(HeadersKey).As(); - var index = 0; - if (headers.Any(x => x.Key == RetryIndexKey)) + var retryAttempt = 0; + if (headers.Any(x => x.Key == RetryAttemptKey)) { - index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + retryAttempt = Serializer.Deserialize(headers.GetLastBytes(RetryAttemptKey)); } - headers.Remove(RetryIndexKey); - headers.Add(RetryIndexKey, Serializer.Serialize(++index)); + headers.Remove(RetryAttemptKey); + headers.Add(RetryAttemptKey, Serializer.Serialize(++retryAttempt)); await context.EventBus.As().PublishAsync(context.EventType, context.EventData, headers); } protected override async Task MoveToDeadLetter(EventExecutionErrorContext context) { - await context.EventBus.As().PublishToDeadLetterAsync(context.EventType, context.EventData, new Headers - { - {"exceptions", Serializer.Serialize(context.Exceptions)} - }); + var headers = context.GetProperty(HeadersKey).As(); + headers.Add("exceptions", Serializer.Serialize(context.Exceptions.Select(x => x.ToString()).ToList())); + await context.EventBus.As().PublishToDeadLetterAsync(context.EventType, context.EventData, headers); } protected override bool ShouldRetry(EventExecutionErrorContext context) @@ -59,16 +58,16 @@ namespace Volo.Abp.EventBus.Kafka return false; } - var headers = context.GetProperty(HeadersKey); + var headers = context.GetProperty(HeadersKey).As(); - if (headers == null) + if (headers.All(x => x.Key != RetryAttemptKey)) { return true; } - var index = Serializer.Deserialize(headers.GetLastBytes(RetryIndexKey)); + var retryAttempt = Serializer.Deserialize(headers.GetLastBytes(RetryAttemptKey)); - return Options.RetryStrategyOptions.MaxRetryAttempts > index; + return Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt; } } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs index 01ec6a3888..03a675d4f2 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.EventBus.RabbitMq public class RabbitMqEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { public const string HeadersKey = "headers"; - public const string RetryIndexKey = "retryIndex"; + public const string RetryAttemptKey = "retryAttempt"; public RabbitMqEventErrorHandler( IOptions options) @@ -30,13 +30,13 @@ namespace Volo.Abp.EventBus.RabbitMq var properties = context.GetProperty(HeadersKey).As(); var headers = properties.Headers ?? new Dictionary(); - var index = 0; - if (headers.ContainsKey(RetryIndexKey)) + var retryAttempt = 0; + if (headers.ContainsKey(RetryAttemptKey)) { - index = (int) headers[RetryIndexKey]; + retryAttempt = (int) headers[RetryAttemptKey]; } - headers[RetryIndexKey] = ++index; + headers[RetryAttemptKey] = ++retryAttempt; headers["exceptions"] = context.Exceptions.Select(x => x.ToString()).ToList(); properties.Headers = headers; @@ -64,14 +64,14 @@ namespace Volo.Abp.EventBus.RabbitMq var properties = context.GetProperty(HeadersKey).As(); - if (properties.Headers == null || !properties.Headers.ContainsKey(RetryIndexKey)) + if (properties.Headers == null || !properties.Headers.ContainsKey(RetryAttemptKey)) { return true; } - var index = (int) properties.Headers[RetryIndexKey]; + var retryAttempt = (int) properties.Headers[RetryAttemptKey]; - return Options.RetryStrategyOptions.MaxRetryAttempts > index; + return Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt; } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs index 83b3f84340..a13f964d3f 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs @@ -13,26 +13,27 @@ namespace Volo.Abp.EventBus.Rebus public override void ConfigureServices(ServiceConfigurationContext context) { var abpEventBusOptions = context.Services.ExecutePreConfiguredActions(); + var options = context.Services.ExecutePreConfiguredActions();; context.Services.AddTransient(typeof(IHandleMessages<>), typeof(RebusDistributedEventHandlerAdapter<>)); Configure(rebusOptions => { context.Services.ExecutePreConfiguredActions(rebusOptions); + }); - context.Services.AddRebus(configure => + context.Services.AddRebus(configure => + { + if (abpEventBusOptions.RetryStrategyOptions != null) { - if (abpEventBusOptions.RetryStrategyOptions != null) - { - configure.Options(b => - b.SimpleRetryStrategy( - errorQueueAddress: abpEventBusOptions.DeadLetterName ?? rebusOptions.InputQueueName + "_dead_letter", - maxDeliveryAttempts: abpEventBusOptions.RetryStrategyOptions.MaxRetryAttempts)); - } + configure.Options(b => + b.SimpleRetryStrategy( + errorQueueAddress: abpEventBusOptions.DeadLetterName ?? options.InputQueueName + "_dead_letter", + maxDeliveryAttempts: abpEventBusOptions.RetryStrategyOptions.MaxRetryAttempts)); + } - rebusOptions.Configurer?.Invoke(configure); - return configure; - }); + options.Configurer?.Invoke(configure); + return configure; }); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index 279765efdb..d1bad00533 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -87,7 +87,6 @@ namespace Volo.Abp.Kafka { await CreateTopicAsync(); Consume(); - Timer.Stop(); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs index 68d1162b7f..4a22fd04f6 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs @@ -21,7 +21,7 @@ namespace Volo.Abp.Kafka string connectionName = null) { var consumer = ServiceScope.ServiceProvider.GetRequiredService(); - consumer.Initialize(topicName, groupId, connectionName); + consumer.Initialize(topicName, deadLetterTopicName, groupId, connectionName); return consumer; } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs index 18af1b758e..6af3f8364f 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.RabbitMQ { public string ExchangeName { get; } - public string DeadLetterExchangeName { get; } + public string DeadLetterExchangeName { get; set; } public string Type { get; } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs index 4fc0aff1ef..f1fcb07255 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.RabbitMQ { [NotNull] public string QueueName { get; } - [NotNull] public string DeadLetterQueueName { get; } + public string DeadLetterQueueName { get; set; } public bool Durable { get; set; } @@ -20,7 +20,7 @@ namespace Volo.Abp.RabbitMQ public QueueDeclareConfiguration( [NotNull] string queueName, - [NotNull] string deadLetterQueueName, + string deadLetterQueueName, bool durable = true, bool exclusive = false, bool autoDelete = false, diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 100a79cc11..671445e00d 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -6,6 +6,7 @@ using RabbitMQ.Client.Events; using System; using System.Collections.Concurrent; using System.Threading.Tasks; +using RabbitMQ.Client.Exceptions; using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.Threading; @@ -178,7 +179,7 @@ namespace Volo.Abp.RabbitMQ Channel.QueueBind(Queue.DeadLetterQueueName, Exchange.DeadLetterExchangeName, Queue.DeadLetterQueueName); } - Channel.QueueDeclare( + var result = Channel.QueueDeclare( queue: Queue.QueueName, durable: Queue.Durable, exclusive: Queue.Exclusive, @@ -197,6 +198,17 @@ namespace Volo.Abp.RabbitMQ } catch (Exception ex) { + if (ex is OperationInterruptedException operationInterruptedException && + operationInterruptedException.ShutdownReason.ReplyCode == 406 && + operationInterruptedException.Message.Contains("arg 'x-dead-letter-exchange'")) + { + Exchange.DeadLetterExchangeName = null; + Queue.DeadLetterQueueName = null; + Queue.Arguments.Remove("x-dead-letter-exchange"); + Queue.Arguments.Remove("x-dead-letter-routing-key"); + Logger.LogWarning("Unable to bind the dead letter queue to an existing queue. You can delete the queue or add policy. See: https://www.rabbitmq.com/parameters.html"); + } + Logger.LogException(ex, LogLevel.Warning); await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); } From 7e686315fad36926fc8c9c9ac13d9469b0dd5b3b Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 28 Apr 2021 16:41:10 +0800 Subject: [PATCH 07/11] Make deadletterName optional --- .../Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs | 8 ++++---- .../Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs | 4 ++-- .../Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index ad672b8b7d..eb774dc4e1 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -66,16 +66,16 @@ namespace Volo.Abp.EventBus.RabbitMq Consumer = MessageConsumerFactory.Create( new ExchangeDeclareConfiguration( AbpRabbitMqEventBusOptions.ExchangeName, - AbpRabbitMqEventBusOptions.ExchangeName + suffix, type: "direct", - durable: true + durable: true, + deadLetterExchangeName: AbpRabbitMqEventBusOptions.ExchangeName + suffix ), new QueueDeclareConfiguration( AbpRabbitMqEventBusOptions.ClientName, - AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix, durable: true, exclusive: false, - autoDelete: false + autoDelete: false, + AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix ), AbpRabbitMqEventBusOptions.ConnectionName ); diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs index 6af3f8364f..b9e762abbe 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs @@ -18,10 +18,10 @@ namespace Volo.Abp.RabbitMQ public ExchangeDeclareConfiguration( string exchangeName, - string deadLetterExchangeName, string type, bool durable = false, - bool autoDelete = false) + bool autoDelete = false, + string deadLetterExchangeName = null) { ExchangeName = exchangeName; DeadLetterExchangeName = deadLetterExchangeName; diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs index f1fcb07255..b84f08ec42 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs @@ -20,18 +20,17 @@ namespace Volo.Abp.RabbitMQ public QueueDeclareConfiguration( [NotNull] string queueName, - string deadLetterQueueName, bool durable = true, bool exclusive = false, bool autoDelete = false, - Dictionary arguments = null) + string deadLetterQueueName = null) { QueueName = queueName; DeadLetterQueueName = deadLetterQueueName; Durable = durable; Exclusive = exclusive; AutoDelete = autoDelete; - Arguments = arguments ?? new Dictionary(); + Arguments = new Dictionary(); } public virtual QueueDeclareOk Declare(IModel channel) From e0e659ae09b2b88a7fea3eda921c41fac65aca55 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 28 Apr 2021 17:34:29 +0800 Subject: [PATCH 08/11] Update KafkaDistributedEventBus --- .../Abp/EventBus/Kafka/KafkaDistributedEventBus.cs | 8 +++++--- .../EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs | 12 ------------ 2 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 0ca0eaebbd..88a28e0641 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -76,10 +76,12 @@ namespace Volo.Abp.EventBus.Kafka return; } - var eventMessage = Serializer.Deserialize(message.Value, eventType); + var eventData = Serializer.Deserialize(message.Value, eventType); - await TriggerHandlersAsync(eventType, eventMessage, - context => { context.SetProperty(KafkaEventErrorHandler.HeadersKey, message.Headers); }); + await TriggerHandlersAsync(eventType, eventData, errorContext => + { + errorContext.SetProperty(KafkaEventErrorHandler.HeadersKey, message.Headers); + }); } public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs deleted file mode 100644 index 7d26f174fe..0000000000 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusOptionsSetup.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Options; - -namespace Volo.Abp.EventBus.Rebus -{ - public class AbpEventBusRebusOptionsSetup : IConfigureOptions - { - public void Configure(AbpEventBusOptions options) - { - throw new System.NotImplementedException(); - } - } -} From 6bd2597b363ac54cbc84cb601e777afc80ee1aaf Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 29 Apr 2021 14:47:03 +0800 Subject: [PATCH 09/11] Refactor --- .../Kafka/KafkaDistributedEventBus.cs | 38 ++++++++++--- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 55 +++++------------- .../RabbitMq/RabbitMqDistributedEventBus.cs | 31 +++++++++- .../RabbitMq/RabbitMqEventErrorHandler.cs | 56 +++++-------------- .../EventBus/Rebus/RebusEventErrorHandler.cs | 24 ++------ .../Volo.Abp.EventBus.csproj | 1 + .../Volo/Abp/EventBus/AbpEventBusModule.cs | 4 +- .../Volo/Abp/EventBus/EventBusBase.cs | 3 +- .../Abp/EventBus/EventErrorHandlerBase.cs | 39 ++++++++----- .../EventBus/EventExecutionErrorContext.cs | 19 ++++++- .../Volo/Abp/EventBus/Local/LocalEventBus.cs | 11 +++- .../EventBus/Local/LocalEventErrorHandler.cs | 35 +++++------- .../Local/EventBus_Exception_Handler_Tests.cs | 29 +++++----- .../EventBus/MyExceptionHandleEventData.cs | 6 +- 14 files changed, 180 insertions(+), 171 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 88a28e0641..0b228d676c 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -80,7 +80,15 @@ namespace Volo.Abp.EventBus.Kafka await TriggerHandlersAsync(eventType, eventData, errorContext => { - errorContext.SetProperty(KafkaEventErrorHandler.HeadersKey, message.Headers); + var retryAttempt = 0; + if (message.Headers.TryGetLastBytes(EventErrorHandlerBase.RetryAttemptKey, out var retryAttemptBytes)) + { + retryAttempt = Serializer.Deserialize(retryAttemptBytes); + } + + errorContext.EventData = Serializer.Deserialize(message.Value, eventType); + errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, message.Headers); + errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt); }); } @@ -159,26 +167,28 @@ namespace Volo.Abp.EventBus.Kafka public override async Task PublishAsync(Type eventType, object eventData) { - await PublishAsync(eventType, eventData, new Headers {{"messageId", Serializer.Serialize(Guid.NewGuid())}}); + await PublishAsync(eventType, eventData, new Headers {{"messageId", Serializer.Serialize(Guid.NewGuid())}}, null); } - public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers) + public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) { - await PublishAsync(AbpKafkaEventBusOptions.TopicName, eventType, eventData, headers); + await PublishAsync(AbpKafkaEventBusOptions.TopicName, eventType, eventData, headers, headersArguments); } - public virtual async Task PublishToDeadLetterAsync(Type eventType, object eventData, Headers headers) + public virtual async Task PublishToDeadLetterAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) { - await PublishAsync(DeadLetterTopicName, eventType, eventData, headers); + await PublishAsync(DeadLetterTopicName, eventType, eventData, headers, headersArguments); } - private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) + private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary headersArguments) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); + SetEventMessageHeaders(headers, headersArguments); + await producer.ProduceAsync( topicName, new Message @@ -187,6 +197,20 @@ namespace Volo.Abp.EventBus.Kafka }); } + private void SetEventMessageHeaders(Headers headers, Dictionary headersArguments) + { + if (headersArguments == null) + { + return; + } + + foreach (var header in headersArguments) + { + headers.Remove(header.Key); + headers.Add(header.Key, Serializer.Serialize(header.Value)); + } + } + private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index d5e4452618..26b8dd17ee 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -1,26 +1,19 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Confluent.Kafka; using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; -using Volo.Abp.Kafka; namespace Volo.Abp.EventBus.Kafka { public class KafkaEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { - public const string HeadersKey = "headers"; - public const string RetryAttemptKey = "retryAttempt"; - - protected IKafkaSerializer Serializer { get; } - public KafkaEventErrorHandler( - IOptions options, - IKafkaSerializer serializer) : base(options) + IOptions options) : base(options) { - Serializer = serializer; } protected override async Task Retry(EventExecutionErrorContext context) @@ -30,44 +23,22 @@ namespace Volo.Abp.EventBus.Kafka await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); } - var headers = context.GetProperty(HeadersKey).As(); - - var retryAttempt = 0; - if (headers.Any(x => x.Key == RetryAttemptKey)) - { - retryAttempt = Serializer.Deserialize(headers.GetLastBytes(RetryAttemptKey)); - } + context.TryGetRetryAttempt(out var retryAttempt); - headers.Remove(RetryAttemptKey); - headers.Add(RetryAttemptKey, Serializer.Serialize(++retryAttempt)); - - await context.EventBus.As().PublishAsync(context.EventType, context.EventData, headers); + await context.EventBus.As().PublishAsync( + context.EventType, + context.EventData, + context.GetProperty(HeadersKey).As(), + new Dictionary {{RetryAttemptKey, ++retryAttempt}}); } protected override async Task MoveToDeadLetter(EventExecutionErrorContext context) { - var headers = context.GetProperty(HeadersKey).As(); - headers.Add("exceptions", Serializer.Serialize(context.Exceptions.Select(x => x.ToString()).ToList())); - await context.EventBus.As().PublishToDeadLetterAsync(context.EventType, context.EventData, headers); - } - - protected override bool ShouldRetry(EventExecutionErrorContext context) - { - if (!base.ShouldRetry(context)) - { - return false; - } - - var headers = context.GetProperty(HeadersKey).As(); - - if (headers.All(x => x.Key != RetryAttemptKey)) - { - return true; - } - - var retryAttempt = Serializer.Deserialize(headers.GetLastBytes(RetryAttemptKey)); - - return Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt; + await context.EventBus.As().PublishToDeadLetterAsync( + context.EventType, + context.EventData, + context.GetProperty(HeadersKey).As(), + new Dictionary {{"exceptions", context.Exceptions.Select(x => x.ToString()).ToList()}}); } } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index eb774dc4e1..541fce1334 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -98,7 +98,16 @@ namespace Volo.Abp.EventBus.RabbitMq await TriggerHandlersAsync(eventType, eventData, errorContext => { - errorContext.SetProperty("headers", ea.BasicProperties); + var retryAttempt = 0; + if (ea.BasicProperties.Headers != null && + ea.BasicProperties.Headers.ContainsKey(EventErrorHandlerBase.RetryAttemptKey)) + { + retryAttempt = (int)ea.BasicProperties.Headers[EventErrorHandlerBase.RetryAttemptKey]; + } + + errorContext.EventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); + errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, ea.BasicProperties); + errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt); }); } @@ -185,8 +194,9 @@ namespace Volo.Abp.EventBus.RabbitMq await PublishAsync(eventType, eventData, null); } - public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties) + public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) { + var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); @@ -205,6 +215,8 @@ namespace Volo.Abp.EventBus.RabbitMq properties.MessageId = Guid.NewGuid().ToString("N"); } + SetEventMessageHeaders(properties, headersArguments); + channel.BasicPublish( exchange: AbpRabbitMqEventBusOptions.ExchangeName, routingKey: eventName, @@ -217,6 +229,21 @@ namespace Volo.Abp.EventBus.RabbitMq return Task.CompletedTask; } + private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) + { + if (headersArguments == null) + { + return; + } + + properties.Headers ??= new Dictionary(); + + foreach (var header in headersArguments) + { + properties.Headers[header.Key] = header.Value; + } + } + private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs index 03a675d4f2..1a7a10dcae 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -11,9 +11,6 @@ namespace Volo.Abp.EventBus.RabbitMq { public class RabbitMqEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { - public const string HeadersKey = "headers"; - public const string RetryAttemptKey = "retryAttempt"; - public RabbitMqEventErrorHandler( IOptions options) : base(options) @@ -27,51 +24,24 @@ namespace Volo.Abp.EventBus.RabbitMq await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); } - var properties = context.GetProperty(HeadersKey).As(); - var headers = properties.Headers ?? new Dictionary(); - - var retryAttempt = 0; - if (headers.ContainsKey(RetryAttemptKey)) - { - retryAttempt = (int) headers[RetryAttemptKey]; - } - - headers[RetryAttemptKey] = ++retryAttempt; - headers["exceptions"] = context.Exceptions.Select(x => x.ToString()).ToList(); - properties.Headers = headers; - - await context.EventBus.As().PublishAsync(context.EventType, context.EventData, properties); + context.TryGetRetryAttempt(out var retryAttempt); + + await context.EventBus.As().PublishAsync( + context.EventType, + context.EventData, + context.GetProperty(HeadersKey).As(), + new Dictionary + { + {RetryAttemptKey, ++retryAttempt}, + {"exceptions", context.Exceptions.Select(x => x.ToString()).ToList()} + }); } protected override Task MoveToDeadLetter(EventExecutionErrorContext context) { - if (context.Exceptions.Count == 1) - { - context.Exceptions[0].ReThrow(); - } - - throw new AggregateException( - "More than one error has occurred while triggering the event: " + context.EventType, - context.Exceptions); - } - - protected override bool ShouldRetry(EventExecutionErrorContext context) - { - if (!base.ShouldRetry(context)) - { - return false; - } - - var properties = context.GetProperty(HeadersKey).As(); - - if (properties.Headers == null || !properties.Headers.ContainsKey(RetryAttemptKey)) - { - return true; - } - - var retryAttempt = (int) properties.Headers[RetryAttemptKey]; + ThrowOriginalExceptions(context); - return Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt; + return Task.CompletedTask; } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs index c75715ebf8..186fb5b5fc 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs @@ -1,10 +1,12 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; namespace Volo.Abp.EventBus.Rebus { + /// + /// Rebus will automatic retries and error handling: https://github.com/rebus-org/Rebus/wiki/Automatic-retries-and-error-handling + /// public class RebusEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { public RebusEventErrorHandler( @@ -15,30 +17,16 @@ namespace Volo.Abp.EventBus.Rebus protected override Task Retry(EventExecutionErrorContext context) { - Throw(context); + ThrowOriginalExceptions(context); return Task.CompletedTask; } protected override Task MoveToDeadLetter(EventExecutionErrorContext context) { - Throw(context); + ThrowOriginalExceptions(context); return Task.CompletedTask; } - - private void Throw(EventExecutionErrorContext context) - { - // Rebus will automatic retries and error handling: https://github.com/rebus-org/Rebus/wiki/Automatic-retries-and-error-handling - - if (context.Exceptions.Count == 1) - { - context.Exceptions[0].ReThrow(); - } - - throw new AggregateException( - "More than one error has occurred while triggering the event: " + context.EventType, - context.Exceptions); - } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj index 221454bed3..261b10d8ae 100644 --- a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj +++ b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index 014308c8e6..329c26c93f 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Volo.Abp.EventBus.Abstractions; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; +using Volo.Abp.Json; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; @@ -12,7 +13,8 @@ namespace Volo.Abp.EventBus { [DependsOn( typeof(AbpEventBusAbstractionsModule), - typeof(AbpMultiTenancyModule))] + typeof(AbpMultiTenancyModule), + typeof(AbpJsonModule))] public class AbpEventBusModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index 6dcaa496b4..df477df609 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Collections; using Volo.Abp.EventBus.Distributed; -using Volo.Abp.EventBus.Local; using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; @@ -104,7 +103,7 @@ namespace Volo.Abp.EventBus if (exceptions.Any()) { - var context = new EventExecutionErrorContext(exceptions, eventData, eventType, this); + var context = new EventExecutionErrorContext(exceptions, eventType, this); onErrorAction?.Invoke(context); await ErrorHandler.Handle(context); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs index 32240eb0e5..c557e3c85c 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -6,6 +6,9 @@ namespace Volo.Abp.EventBus { public abstract class EventErrorHandlerBase : IEventErrorHandler { + public const string HeadersKey = "headers"; + public const string RetryAttemptKey = "retryAttempt"; + protected AbpEventBusOptions Options { get; } protected EventErrorHandlerBase(IOptions options) @@ -17,14 +20,7 @@ namespace Volo.Abp.EventBus { if (!ShouldHandle(context)) { - if (context.Exceptions.Count == 1) - { - context.Exceptions[0].ReThrow(); - } - - throw new AggregateException( - "More than one error has occurred while triggering the event: " + context.EventType, - context.Exceptions); + ThrowOriginalExceptions(context); } if (ShouldRetry(context)) @@ -47,17 +43,34 @@ namespace Volo.Abp.EventBus return false; } - if (Options.ErrorHandleSelector != null) + return Options.ErrorHandleSelector == null || Options.ErrorHandleSelector.Invoke(context.EventType); + } + + protected virtual bool ShouldRetry(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions == null) + { + return false; + } + + if (!context.TryGetRetryAttempt(out var retryAttempt)) { - return Options.ErrorHandleSelector.Invoke(context.EventType); + return false; } - return true; + return Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt; } - protected virtual bool ShouldRetry(EventExecutionErrorContext context) + protected virtual void ThrowOriginalExceptions(EventExecutionErrorContext context) { - return Options.RetryStrategyOptions != null; + if (context.Exceptions.Count == 1) + { + context.Exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + context.EventType, + context.Exceptions); } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs index 29c362c1ca..e192f61cbe 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Volo.Abp.Data; using Volo.Abp.ObjectExtending; namespace Volo.Abp.EventBus @@ -8,18 +9,30 @@ namespace Volo.Abp.EventBus { public IReadOnlyList Exceptions { get; } - public object EventData { get; } + public object EventData { get; set; } public Type EventType { get; } public IEventBus EventBus { get; } - public EventExecutionErrorContext(List exceptions, object eventData, Type eventType, IEventBus eventBus) + public EventExecutionErrorContext(List exceptions, Type eventType, IEventBus eventBus) { Exceptions = exceptions; - EventData = eventData; EventType = eventType; EventBus = eventBus; } + + public bool TryGetRetryAttempt(out int retryAttempt) + { + retryAttempt = 0; + if (!this.HasProperty(EventErrorHandlerBase.RetryAttemptKey)) + { + return false; + } + + retryAttempt = this.GetProperty(EventErrorHandlerBase.RetryAttemptKey); + return true; + + } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs index 0a0ae4f378..16246a892f 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs @@ -11,6 +11,7 @@ using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Json; namespace Volo.Abp.EventBus.Local { @@ -29,13 +30,17 @@ namespace Volo.Abp.EventBus.Local protected ConcurrentDictionary> HandlerFactories { get; } + protected IJsonSerializer Serializer { get; } + public LocalEventBus( IOptions options, IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant, - IEventErrorHandler errorHandler) + IEventErrorHandler errorHandler, + IJsonSerializer serializer) : base(serviceScopeFactory, currentTenant, errorHandler) { + Serializer = serializer; Options = options.Value; Logger = NullLogger.Instance; @@ -126,9 +131,11 @@ namespace Volo.Abp.EventBus.Local public virtual async Task PublishAsync(LocalEventMessage localEventMessage) { + var rawEventData = Serializer.Serialize(localEventMessage.EventData); await TriggerHandlersAsync(localEventMessage.EventType, localEventMessage.EventData, errorContext => { - errorContext.SetProperty("messageId", localEventMessage.MessageId); + errorContext.EventData = Serializer.Deserialize(localEventMessage.EventType, rawEventData); + errorContext.SetProperty(nameof(LocalEventMessage.MessageId), localEventMessage.MessageId); }); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs index 79fd405940..662406a720 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -7,6 +7,7 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.EventBus.Local { + [ExposeServices(typeof(LocalEventErrorHandler), typeof(IEventErrorHandler))] public class LocalEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { protected Dictionary RetryTracking { get; } @@ -25,7 +26,10 @@ namespace Volo.Abp.EventBus.Local await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); } - var messageId = context.GetProperty("messageId"); + var messageId = context.GetProperty(nameof(LocalEventMessage.MessageId)); + + context.TryGetRetryAttempt(out var retryAttempt); + RetryTracking[messageId] = ++retryAttempt; await context.EventBus.As().PublishAsync(new LocalEventMessage(messageId, context.EventData, context.EventType)); @@ -34,36 +38,23 @@ namespace Volo.Abp.EventBus.Local protected override Task MoveToDeadLetter(EventExecutionErrorContext context) { - if (context.Exceptions.Count == 1) - { - context.Exceptions[0].ReThrow(); - } + ThrowOriginalExceptions(context); - throw new AggregateException( - "More than one error has occurred while triggering the event: " + context.EventType, - context.Exceptions); + return Task.CompletedTask; } protected override bool ShouldRetry(EventExecutionErrorContext context) { - if (!base.ShouldRetry(context)) - { - return false; - } + var messageId = context.GetProperty(nameof(LocalEventMessage.MessageId)); + context.SetProperty(RetryAttemptKey, RetryTracking.GetOrDefault(messageId)); - var messageId = context.GetProperty("messageId"); - - var index = RetryTracking.GetOrDefault(messageId); - - if (Options.RetryStrategyOptions.MaxRetryAttempts <= index) + if (base.ShouldRetry(context)) { - RetryTracking.Remove(messageId); - return false; + return true; } - RetryTracking[messageId] = ++index; - - return true; + RetryTracking.Remove(messageId); + return false; } } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs index 7482606416..d6b61ee863 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_Exception_Handler_Tests.cs @@ -10,11 +10,10 @@ namespace Volo.Abp.EventBus.Local [Fact] public async Task Should_Not_Handle_Exception() { - MySimpleEventData data = null; + var retryAttempt = 0; LocalEventBus.Subscribe(eventData => { - ++eventData.Value; - data = eventData; + retryAttempt++; throw new Exception("This exception is intentionally thrown!"); }); @@ -23,20 +22,21 @@ namespace Volo.Abp.EventBus.Local await LocalEventBus.PublishAsync(new MySimpleEventData(1)); }); - data.Value.ShouldBe(2); + retryAttempt.ShouldBe(1); appException.Message.ShouldBe("This exception is intentionally thrown!"); } [Fact] public async Task Should_Handle_Exception() { - MyExceptionHandleEventData data = null; + var retryAttempt = 0; LocalEventBus.Subscribe(eventData => { - ++eventData.RetryAttempts; - data = eventData; + eventData.Value.ShouldBe(0); - if (eventData.RetryAttempts < 2) + retryAttempt++; + eventData.Value++; + if (retryAttempt < 2) { throw new Exception("This exception is intentionally thrown!"); } @@ -46,17 +46,20 @@ namespace Volo.Abp.EventBus.Local }); await LocalEventBus.PublishAsync(new MyExceptionHandleEventData(0)); - data.RetryAttempts.ShouldBe(2); + retryAttempt.ShouldBe(2); } [Fact] public async Task Should_Throw_Exception_After_Error_Handle() { - MyExceptionHandleEventData data = null; + var retryAttempt = 0; LocalEventBus.Subscribe(eventData => { - ++eventData.RetryAttempts; - data = eventData; + eventData.Value.ShouldBe(0); + + retryAttempt++; + eventData.Value++; + throw new Exception("This exception is intentionally thrown!"); }); @@ -65,7 +68,7 @@ namespace Volo.Abp.EventBus.Local await LocalEventBus.PublishAsync(new MyExceptionHandleEventData(0)); }); - data.RetryAttempts.ShouldBe(4); + retryAttempt.ShouldBe(4); appException.Message.ShouldBe("This exception is intentionally thrown!"); } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs index b68f60aecd..f490d58211 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MyExceptionHandleEventData.cs @@ -2,11 +2,11 @@ { public class MyExceptionHandleEventData { - public int RetryAttempts { get; set; } + public int Value { get; set; } - public MyExceptionHandleEventData(int retryAttempts) + public MyExceptionHandleEventData(int value) { - RetryAttempts = retryAttempts; + Value = value; } } } From 450f52e18aa5880b124a3a5155140486d3083621 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 6 May 2021 10:47:38 +0800 Subject: [PATCH 10/11] write log when message move to dead letter --- .../Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index 26b8dd17ee..774973c649 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Confluent.Kafka; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -11,9 +13,12 @@ namespace Volo.Abp.EventBus.Kafka { public class KafkaEventErrorHandler : EventErrorHandlerBase, ISingletonDependency { + protected ILogger Logger { get; set; } + public KafkaEventErrorHandler( IOptions options) : base(options) { + Logger = NullLogger.Instance; } protected override async Task Retry(EventExecutionErrorContext context) @@ -34,6 +39,10 @@ namespace Volo.Abp.EventBus.Kafka protected override async Task MoveToDeadLetter(EventExecutionErrorContext context) { + Logger.LogException( + context.Exceptions.Count == 1 ? context.Exceptions.First() : new AggregateException(context.Exceptions), + LogLevel.Error); + await context.EventBus.As().PublishToDeadLetterAsync( context.EventType, context.EventData, From abc400ce9b65cd46930c538817b1222a2ebbe812 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 17 Jun 2021 10:18:30 +0800 Subject: [PATCH 11/11] Using asynchronous methods --- .../EventBus/Kafka/KafkaEventErrorHandler.cs | 4 +-- .../RabbitMq/RabbitMqEventErrorHandler.cs | 4 +-- .../EventBus/Rebus/RebusEventErrorHandler.cs | 4 +-- .../Volo/Abp/EventBus/EventBusBase.cs | 2 +- .../Abp/EventBus/EventErrorHandlerBase.cs | 28 +++++++++---------- .../Volo/Abp/EventBus/IEventErrorHandler.cs | 2 +- .../EventBus/Local/LocalEventErrorHandler.cs | 8 +++--- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs index 774973c649..aee21f75a9 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -21,7 +21,7 @@ namespace Volo.Abp.EventBus.Kafka Logger = NullLogger.Instance; } - protected override async Task Retry(EventExecutionErrorContext context) + protected override async Task RetryAsync(EventExecutionErrorContext context) { if (Options.RetryStrategyOptions.IntervalMillisecond > 0) { @@ -37,7 +37,7 @@ namespace Volo.Abp.EventBus.Kafka new Dictionary {{RetryAttemptKey, ++retryAttempt}}); } - protected override async Task MoveToDeadLetter(EventExecutionErrorContext context) + protected override async Task MoveToDeadLetterAsync(EventExecutionErrorContext context) { Logger.LogException( context.Exceptions.Count == 1 ? context.Exceptions.First() : new AggregateException(context.Exceptions), diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs index 1a7a10dcae..e8848bee72 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.EventBus.RabbitMq { } - protected override async Task Retry(EventExecutionErrorContext context) + protected override async Task RetryAsync(EventExecutionErrorContext context) { if (Options.RetryStrategyOptions.IntervalMillisecond > 0) { @@ -37,7 +37,7 @@ namespace Volo.Abp.EventBus.RabbitMq }); } - protected override Task MoveToDeadLetter(EventExecutionErrorContext context) + protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context) { ThrowOriginalExceptions(context); diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs index 186fb5b5fc..8fe6a53dbb 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs @@ -15,14 +15,14 @@ namespace Volo.Abp.EventBus.Rebus { } - protected override Task Retry(EventExecutionErrorContext context) + protected override Task RetryAsync(EventExecutionErrorContext context) { ThrowOriginalExceptions(context); return Task.CompletedTask; } - protected override Task MoveToDeadLetter(EventExecutionErrorContext context) + protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context) { ThrowOriginalExceptions(context); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index df477df609..78d226538e 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -105,7 +105,7 @@ namespace Volo.Abp.EventBus { var context = new EventExecutionErrorContext(exceptions, eventType, this); onErrorAction?.Invoke(context); - await ErrorHandler.Handle(context); + await ErrorHandler.HandleAsync(context); } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs index c557e3c85c..ba4527fc70 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -16,49 +16,49 @@ namespace Volo.Abp.EventBus Options = options.Value; } - public virtual async Task Handle(EventExecutionErrorContext context) + public virtual async Task HandleAsync(EventExecutionErrorContext context) { - if (!ShouldHandle(context)) + if (!await ShouldHandleAsync(context)) { ThrowOriginalExceptions(context); } - if (ShouldRetry(context)) + if (await ShouldRetryAsync(context)) { - await Retry(context); + await RetryAsync(context); return; } - await MoveToDeadLetter(context); + await MoveToDeadLetterAsync(context); } - protected abstract Task Retry(EventExecutionErrorContext context); + protected abstract Task RetryAsync(EventExecutionErrorContext context); - protected abstract Task MoveToDeadLetter(EventExecutionErrorContext context); + protected abstract Task MoveToDeadLetterAsync(EventExecutionErrorContext context); - protected virtual bool ShouldHandle(EventExecutionErrorContext context) + protected virtual Task ShouldHandleAsync(EventExecutionErrorContext context) { if (!Options.EnabledErrorHandle) { - return false; + return Task.FromResult(false); } - return Options.ErrorHandleSelector == null || Options.ErrorHandleSelector.Invoke(context.EventType); + return Task.FromResult(Options.ErrorHandleSelector == null || Options.ErrorHandleSelector.Invoke(context.EventType)); } - protected virtual bool ShouldRetry(EventExecutionErrorContext context) + protected virtual Task ShouldRetryAsync(EventExecutionErrorContext context) { if (Options.RetryStrategyOptions == null) { - return false; + return Task.FromResult(false); } if (!context.TryGetRetryAttempt(out var retryAttempt)) { - return false; + return Task.FromResult(false); } - return Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt; + return Task.FromResult(Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt); } protected virtual void ThrowOriginalExceptions(EventExecutionErrorContext context) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs index 27d4951ead..f1b4a40f15 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs @@ -4,6 +4,6 @@ namespace Volo.Abp.EventBus { public interface IEventErrorHandler { - Task Handle(EventExecutionErrorContext context); + Task HandleAsync(EventExecutionErrorContext context); } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs index 662406a720..c08bca019f 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.EventBus.Local RetryTracking = new Dictionary(); } - protected override async Task Retry(EventExecutionErrorContext context) + protected override async Task RetryAsync(EventExecutionErrorContext context) { if (Options.RetryStrategyOptions.IntervalMillisecond > 0) { @@ -36,19 +36,19 @@ namespace Volo.Abp.EventBus.Local RetryTracking.Remove(messageId); } - protected override Task MoveToDeadLetter(EventExecutionErrorContext context) + protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context) { ThrowOriginalExceptions(context); return Task.CompletedTask; } - protected override bool ShouldRetry(EventExecutionErrorContext context) + protected override async Task ShouldRetryAsync(EventExecutionErrorContext context) { var messageId = context.GetProperty(nameof(LocalEventMessage.MessageId)); context.SetProperty(RetryAttemptKey, RetryTracking.GetOrDefault(messageId)); - if (base.ShouldRetry(context)) + if (await base.ShouldRetryAsync(context)) { return true; }