An event bus for
in-process publish-subscribe communication between components. It aids in de-coupling publishers and subscribers and provides a simple way of publishing, filtering and listening to events.
The various facets of the event bus is defined as follows:
Publishers
Publisher of events can publish the events in two modes as follows:
- Unconditional: The event gets published irrespective of the fact whether there are any listeners or not. If there are no listeners, the event will be discarded.
- Conditional: The event will be published iff there is atleast one listener for that event at the time publish is called. This style should be used only when creating the event object itself is costly, otherwise, the complexity is not worth the effort. In this case, the event creation is delayed using {@link EventCreator}. In case, a listener that showed interest in the event unregisters before receiving the same, it will not receive this event. On the other hand, if any new listener gets registered for this event after the initial check is made, it will receive this event, provided it gets registered before the event starts to get published. The interest of the listener for this event will only be based on the event type and not the event data as the data is not available initially. This essentially means that such an event may be filtered-out by the listener based on the event data when it actually receives the event.
Events
An event can be any arbitrary object and does not require any semantics.
Subscribers
Subscribers are the components that are interested in events generated by the publishers. A subscriber requests interest in certain events by providing a method with the annotation {@link Subscribe} and having just one argument, the event object itself.
Event - Subscriber mapping
Any subscriber method taking an event object of Class X as argument is expected to be interested in all events published corresponding to its superclasses and implemented interface (directly or via a superclass). In short if the invocation of {@link Class#isAssignableFrom(Class)} on the event object's class withthe subscriber class as argument returns
true
, then that subscriber is interested in the event.
Dispatch mode
All subscribers are async and isolated with any other listener i.e. a slow listener does not impact other listeners for the same event. At the same time, the event data is not copied for this isolation. The overhead of this isolation will just be the cost of storing references to these events in each listeners queue.
A subscriber can indicate if it favors synchronous event consumption: This is just a backdoor and should be used with caution as then the consumption and filtering will happen in the publishing thread. Also, this behavior can be overridden if the property {@link SyncSubscribersGatekeeper#ALLOW_SYNC_SUBSCRIBERS} is set to
false
.
In that case, theevents will be sent to the subscriber asynchronously. See {@link SyncSubscribersGatekeeper} for more details. There are multiple facets of this async dispatch mode which are described below:
- Slow subscribers: In case the subscriber queue is full, the older events are rejected, one at a time and the event bus retries to offer the event to the subscriber, after each removal. This retry is capped at a default value {@link EventBus#CONSUMER_QUEUE_FULL_RETRY_MAX_DEFAULT} which can be overridden by a dynamic property:{@link EventBus#CONSUMER_QUEUE_FULL_RETRY_MAX_PROP_NAME}. If all the retries fail, the event is rejected.
- Event batch: If the subscribers wish to process the events in batches, then they can annotate themselves with an appropriate {@link com.netflix.eventbus.spi.Subscribe.BatchingStrategy}. Care must be taken while batching as the rejections also happens in batch when the subscribers are slow.
- Queue size: A subscriber can optionally define a queue size for the async dispatch using {@link com.netflix.eventbus.spi.Subscribe#queueSize()}. The default value for this queue size is {@link EventBus#CONSUMER_QUEUE_SIZE_DEFAULT} which can be changed via the fast property:{@link EventBus#CONSUMER_QUEUE_SIZE_DEFAULT_PROP_NAME}
Event filtering
Events can be filtered both before publishing and before receiving the same in the subscriber. Filtering at the subscriber end is done per subscriber. Filtering at the publisher side rejects the event.
All filters for the event must return true for the event to be published/received Event filters can be expressed as a filter language as available under {@link com.netflix.eventbus.filter.lang}. Such a filter can then be converted into an {@link EventFilter} using {@link com.netflix.eventbus.filter.EventFilterCompiler}For relatively easy way of programmatic creation of filters, one can use the utility/factory class {@link com.netflix.eventbus.filter.EventFilters}.
Subscriber level filtering is done in the consumer thread and not in the thread of the event publisher. This alleviates the overhead of event publishing that may be introduced by slow filter evaluation. However, it does have a side effect on the speed of event processing by the consumer and hence may increase the backlog for particular consumers. In any case, the impact of slow filters is isolated to the consumers and does not plague the event publishing that typically will happen in the client's request processing thread.
Runtime event - subscriber binding
Eventbus subscribers, by design, are statically tied to a particular type of object i.e. the class of the event it is interested in. This in most cases is beneficial and easy to use, however, in some cases (typically event agnostic, middlemen, which stores the event for later investigation), the event processing is much the same irrespective of the type of event it receives. In such a case, it will be an overhead (even if possible) to define a subscriber for each kind of event existing in the system. {@link DynamicSubscriber} is a way to achieve runtime binding of an eventto a subscriber.
@author Nitesh Kant (nkant@netflix.com)