@
原理
本地事件匯流排是透過Ioc容器來實現的。
IEventBus介面定義了事件匯流排的基本功能,如註冊事件、取消註冊事件、觸發事件等。
Abp.Events.Bus.EventBus是本地事件匯流排的實現類,其中私有成員ConcurrentDictionary<Type, List<IEventHandlerFactory>> _handlerFactories
是事件訂閱表。透過維護事件訂閱表來實現事件處理器的註冊和取消註冊。當對應型別的事件觸發時,透過訂閱表查詢所有事件處理器,透過Ioc容器來獲取處理器例項,然後透過反射來呼叫事件處理器的"HandleEvent"方法。
建立分散式事件匯流排
首先,我們需要一個分散式事件匯流排中介軟體,用來將事件從本地事件匯流排轉發到分散式事件匯流排。常用的中介軟體有RabbitMQ、Kafka、Redis等。
開源社群已經有實現好的庫,本專案參考了 wuyi6216/Abp.RemoteEventBus
這裡已經定義好了一個分散式事件匯流排介面
public interface IDistributedEventBus : IDisposable
{
void MessageHandle(string topic, string message);
void Publish(IDistributedEventData eventData);
void Subscribe(string topic);
void Unsubscribe(string topic);
void UnsubscribeAll();
}
為了相容本地事件匯流排,我們需要定義一個分散式事件匯流排介面,繼承自IEventBus介面。
public interface IMultipleEventBus : IDistributedEventBus, IEventBus
{
}
實現自動訂閱和事件轉發
當註冊本地事件時,將訂閱分散式事件,事件Topic為型別的字串表現形式
public IDisposable Register(Type eventType, IEventHandlerFactory factory)
{
GetOrCreateHandlerFactories(eventType);
List<IEventHandlerFactory> currentLists;
if (_handlerFactories.TryGetValue(eventType, out currentLists))
{
lock (currentLists)
{
if (currentLists.Count == 0)
{
//Register to distributed event
this.Subscribe(eventType.ToString());
}
currentLists.Add(factory);
}
}
return new FactoryUnregistrar(this, eventType, factory);
}
建立TriggerRemote,此方法用於將本地事件引數打包成為分散式事件訊息payload,併發布該訊息
public void TriggerRemote(Type eventType, object eventSource, IEventData eventData)
{
var exceptions = new List<Exception>();
eventData.EventSource = eventSource;
try
{
var payloadDictionary = new Dictionary<string, object>
{
{ PayloadKey, eventData }
};
var distributedeventData = new DistributedEventData(eventType.ToString(), payloadDictionary);
Publish(distributedeventData);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
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);
}
}
當觸發本地事件時,將訊息轉發至分散式事件匯流排。
在Trigger方法中呼叫TriggerRemote,事件狀態回撥和事件異常回撥將不會被轉發。
if (!(typeof(DistributedEventBusEvent) == eventType
|| typeof(DistributedEventBusEvent).IsAssignableFrom(eventType)
|| typeof(DistributedEventMessageHandleExceptionData) == eventType
|| typeof(DistributedEventHandleExceptionData) == eventType
))
{
if (typeof(DistributedEventArgs) != eventType)
{
TriggerRemote(eventType, eventSource, eventData);
}
}
在消費端接收到分散式事件訊息時,從Topic中解析型別,轉發給本地事件。若此型別在本地事件註冊過,則將訊息反序列化為本地事件引數,然後觸發本地事件。
本地事件處理器將觸發最終的處理方法。
public virtual void MessageHandle(string topic, string message)
{
Logger.Debug($"Receive message on topic {topic}");
try
{
var eventData = _remoteEventSerializer.Deserialize<DistributedEventData>(message);
var eventArgs = new DistributedEventArgs(eventData, topic, message);
Trigger(this, new DistributedEventBusHandlingEvent(eventArgs));
if (!string.IsNullOrEmpty(eventData.Type))
{
string pattern = @"(.*?)\[(.*?)\]";
Match match = Regex.Match(eventData.Type, pattern);
if (match.Success)
{
var type = match.Groups[1].Value;
var type2 = match.Groups[2].Value;
var localTriggerType = typeFinder.Find(c => c.FullName == type).FirstOrDefault();
var genericType = typeFinder.Find(c => c.FullName == type2).FirstOrDefault();
if (localTriggerType != null && genericType != null)
{
if (localTriggerType.GetTypeInfo().IsGenericType
&& localTriggerType.GetGenericArguments().Length == 1
&& !genericType.IsAbstract && !genericType.IsInterface
)
{
var localTriggerGenericType = localTriggerType.GetGenericTypeDefinition().MakeGenericType(genericType);
if (eventData.Data.TryGetValue(PayloadKey, out var payload))
{
var payloadObject = (payload as JObject).ToObject(localTriggerGenericType);
Trigger(localTriggerGenericType, this, (IEventData)payloadObject);
}
}
}
}
else
{
var localTriggerType = typeFinder.Find(c => c.FullName == eventData.Type).FirstOrDefault();
if (localTriggerType != null && !localTriggerType.IsAbstract && !localTriggerType.IsInterface)
{
if (eventData.Data.TryGetValue(PayloadKey, out var payload))
{
var payloadObject = (payload as JObject).ToObject(localTriggerType);
Trigger(localTriggerType, this, (IEventData)payloadObject);
}
}
}
Trigger(this, new DistributedEventBusHandledEvent(eventArgs));
}
}
catch (Exception ex)
{
Logger.Error("Consume remote message exception", ex);
Trigger(this, new DistributedEventMessageHandleExceptionData(ex, topic, topic));
}
}
使用
DistributedEventBus有不同的實現方式,這裡以Redis為例
啟動Redis服務
下載Redis並啟動服務,使用預設埠6379
配置
生產者和消費者端都需要配置分散式事件匯流排
首先引用Abp.DistributedEventBus.Redis,並配置Abp模組依賴
[DependsOn(typeof(AbpDistributedEventBusRedisModule))]
在PreInitialize方法中配置Redis連線資訊
Configuration.Modules.DistributedEventBus().UseRedis().Configure(setting =>
{
setting.Server = "127.0.0.1:6379";
});
用MultipleEventBus替換Abp預設事件匯流排
//todo: 事件匯流排
Configuration.ReplaceService(
typeof(IEventBus),
() => IocManager.IocContainer.Register(
Component.For<IEventBus>().ImplementedBy<MultipleEventBus>()
));
傳遞Abp預設事件
我們知道在使用倉儲時,Abp會自動觸發一些事件,如建立、更新、刪除等。我們來測試這些事件是否能透過分散式事件匯流排來傳遞。
定義一個實體類,用於傳遞實體的增刪改事件。
public class Person : FullAuditedEntity<int>
{
public string Name { get; set; }
public int Age { get; set; }
public string PhoneNumber { get; set; }
}
在消費者端,定義一個事件處理器,用於處理實體的增刪改事件。
public class RemoteEntityChangedEventHandler :
IEventHandler<EntityUpdatedEventData<Person>>,
IEventHandler<EntityCreatedEventData<Person>>,
IEventHandler<EntityDeletedEventData<Person>>,
ITransientDependency
{
void IEventHandler<EntityUpdatedEventData<Person>>.HandleEvent(EntityUpdatedEventData<Person> eventData)
{
var person = eventData.Entity;
Console.WriteLine($"Remote Entity Updated - Name:{person.Name}, Age:{person.Age}, PhoneNumber:{person.PhoneNumber}");
}
void IEventHandler<EntityCreatedEventData<Person>>.HandleEvent(EntityCreatedEventData<Person> eventData)
{
var person = eventData.Entity;
Console.WriteLine($"Remote Entity Created - Name:{person.Name}, Age:{person.Age}, PhoneNumber:{person.PhoneNumber}");
}
void IEventHandler<EntityDeletedEventData<Person>>.HandleEvent(EntityDeletedEventData<Person> eventData)
{
var person = eventData.Entity;
Console.WriteLine($"Remote Entity Deleted - Name:{person.Name}, Age:{person.Age}, PhoneNumber:{person.PhoneNumber}");
}
}
在生產者端,用IRepository對實體進行增刪改操作。
var person = new Person()
{
Name = "John",
Age = 36,
PhoneNumber = "18588888888"
};
personRepository.Insert(person);
var person2 = new Person()
{
Name = "John2",
Age = 36,
PhoneNumber = "18588888889"
};
personRepository.Insert(person2);
var persons = personRepository.GetAllList();
foreach (var p in persons)
{
p.Age += 1;
personRepository.Update(p);
Console.WriteLine($"Entity Updated - Name:{p.Name}, Age:{p.Age}, PhoneNumber:{p.PhoneNumber}");
}
foreach (var p in persons)
{
personRepository.Delete(p);
Console.WriteLine($"Entity Deleted - Name:{p.Name}, Age:{p.Age}, PhoneNumber:{p.PhoneNumber}");
}
執行程式(同時執行消費者端和生產者端),可以看到消費者端列印出了實體的增刪改事件。
注意:
分散式事件匯流排在兩個獨立系統間傳遞事件,所以需要定義一個共同的型別物件,用於事件引數的傳遞。
因此消費者端需要引用生產者端的模組,以便獲取共同的型別物件。
public override Assembly[] GetAdditionalAssemblies()
{
var clientModuleAssembly = typeof(Person).GetAssembly();
return [clientModuleAssembly];
}
傳遞自定義事件
定義NotificationEventData,用於傳遞自定義事件。
public class NotificationEventData : EventData
{
public int Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public bool IsRead { get; set; }
}
在消費者端,定義一個事件處理器,用於處理自定義事件。
public class NotificationEventHandler :
IEventHandler<NotificationEventData>,
ITransientDependency
{
void IEventHandler<NotificationEventData>.HandleEvent(NotificationEventData eventData)
{
Console.WriteLine($"Id: {eventData.Id}");
Console.WriteLine($"Title: {eventData.Title}");
Console.WriteLine($"Message: {eventData.Message}");
Console.WriteLine($"IsRead: {eventData.IsRead}");
}
}
在生產者端,觸發自定義事件。
var eventBus = IocManager.Instance.Resolve<IEventBus>();
eventBus.Trigger<NotificationEventData>(new NotificationEventData()
{
Title = "Hi",
Message = "Customized definition event test!",
Id = 100,
IsRead = true,
});
執行程式(同時執行消費者端和生產者端),可以看到消費者端列印出了自定義事件。