All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bazaarvoice.emodb.databus.core.DefaultFanout Maven / Gradle / Ivy

package com.bazaarvoice.emodb.databus.core;

import com.bazaarvoice.emodb.common.dropwizard.lifecycle.ServiceFailureListener;
import com.bazaarvoice.emodb.databus.ChannelNames;
import com.bazaarvoice.emodb.databus.model.OwnedSubscription;
import com.bazaarvoice.emodb.datacenter.api.DataCenter;
import com.bazaarvoice.emodb.event.api.EventData;
import com.bazaarvoice.emodb.sor.api.UnknownTableException;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.AbstractScheduledService;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Copies events from the "__system_bus:master" event channel or an inbound replication channel to the individual
 * subscription event channels.
 * 

* Each source channel is handled by a single process in the EmoDB cluster. Generally this copy process is fast enough * (and I/O bound) that it's not necessary to spread the work across different servers, but if that changes we can * spread writes across multiple source channels (eg. __system_bus:master1, __system_bus:master2, etc.). */ public class DefaultFanout extends AbstractScheduledService { private static final Logger _log = LoggerFactory.getLogger(DefaultFanout.class); private static final int FLUSH_EVENTS_THRESHOLD = 500; private final String _name; private final EventSource _eventSource; private final Function, Void> _eventSink; private final boolean _replicateOutbound; private final Duration _sleepWhenIdle; private final Supplier> _subscriptionsSupplier; private final DataCenter _currentDataCenter; private final RateLimitedLog _rateLimitedLog; private final SubscriptionEvaluator _subscriptionEvaluator; private final Meter _eventsRead; private final Meter _eventsWrittenLocal; private final Meter _eventsWrittenOutboundReplication; public DefaultFanout(String name, EventSource eventSource, Function, Void> eventSink, boolean replicateOutbound, Duration sleepWhenIdle, Supplier> subscriptionsSupplier, DataCenter currentDataCenter, RateLimitedLogFactory logFactory, SubscriptionEvaluator subscriptionEvaluator, MetricRegistry metricRegistry) { _name = checkNotNull(name, "name"); _eventSource = checkNotNull(eventSource, "eventSource"); _eventSink = checkNotNull(eventSink, "eventSink"); _replicateOutbound = replicateOutbound; _sleepWhenIdle = checkNotNull(sleepWhenIdle, "sleepWhenIdle"); _subscriptionsSupplier = checkNotNull(subscriptionsSupplier, "subscriptionsSupplier"); _currentDataCenter = checkNotNull(currentDataCenter, "currentDataCenter"); _subscriptionEvaluator = checkNotNull(subscriptionEvaluator, "subscriptionEvaluator"); _rateLimitedLog = logFactory.from(_log); _eventsRead = newEventMeter("read", metricRegistry); _eventsWrittenLocal = newEventMeter("written-local", metricRegistry); _eventsWrittenOutboundReplication = newEventMeter("written-outbound-replication", metricRegistry); ServiceFailureListener.listenTo(this, metricRegistry); } private Meter newEventMeter(String name, MetricRegistry metricRegistry) { String metricName = MetricRegistry.name("bv.emodb.databus", "DefaultFanout", name, _name); return metricRegistry.meter(metricName); } @Override protected Scheduler scheduler() { return Scheduler.newFixedDelaySchedule(0, _sleepWhenIdle.getMillis(), TimeUnit.MILLISECONDS); } @Override protected void runOneIteration() { try { //noinspection StatementWithEmptyBody while (isRunning() && copyEvents()) { // Loop w/o sleeping as long as we keep finding events. } } catch (Throwable t) { // Fanout runs in a continuous loop. If we get into a bad state, use the rate limited log to avoid // flooding the logs with a continuous stream of error messages. Include the event source name in the // message template so we rate limit each event source independently. _rateLimitedLog.error(t, "Unexpected fanout exception copying from " + _name + ": {}", t); stop(); // Give up leadership temporarily. Maybe another server will have more success. } } private boolean copyEvents() { // Use peek() not poll() since LeaderSelector ensures we're not competing with other processes for claims. List rawEvents = _eventSource.get(1000); // If no events, sleep a little while before doing any more work to allow new events to arrive. if (rawEvents.isEmpty()) { return false; } // Last chance to check that we are the leader before doing anything that would be bad if we aren't. return isRunning() && copyEvents(rawEvents); } @VisibleForTesting boolean copyEvents(List rawEvents) { // Read the list of subscriptions *after* reading events from the event store to avoid race conditions with // creating a new subscription. Iterable subscriptions = _subscriptionsSupplier.get(); // Copy the events to all the destination channels. List eventKeys = Lists.newArrayListWithCapacity(rawEvents.size()); ListMultimap eventsByChannel = ArrayListMultimap.create(); int numOutboundReplicationEvents = 0; for (EventData rawEvent : rawEvents) { eventKeys.add(rawEvent.getId()); ByteBuffer eventData = rawEvent.getData(); SubscriptionEvaluator.MatchEventData matchEventData; try { matchEventData = _subscriptionEvaluator.getMatchEventData(eventData); } catch (UnknownTableException e) { continue; } // Copy to subscriptions in the current data center. for (OwnedSubscription subscription : _subscriptionEvaluator.matches(subscriptions, matchEventData)) { eventsByChannel.put(subscription.getName(), eventData); } // Copy to queues for eventual delivery to remote data centers. if (_replicateOutbound) { for (DataCenter dataCenter : matchEventData.getTable().getDataCenters()) { if (!dataCenter.equals(_currentDataCenter)) { String channel = ChannelNames.getReplicationFanoutChannel(dataCenter); eventsByChannel.put(channel, eventData); numOutboundReplicationEvents++; } } } // Flush to cap the amount of memory used to buffer events. if (eventsByChannel.size() >= FLUSH_EVENTS_THRESHOLD) { flush(eventKeys, eventsByChannel, numOutboundReplicationEvents); numOutboundReplicationEvents = 0; } } // Final flush. flush(eventKeys, eventsByChannel, numOutboundReplicationEvents); return true; } private void flush(List eventKeys, Multimap eventsByChannel, int numOutboundReplicationEvents) { if (!eventsByChannel.isEmpty()) { _eventSink.apply(eventsByChannel); _eventsWrittenLocal.mark(eventsByChannel.size() - numOutboundReplicationEvents); _eventsWrittenOutboundReplication.mark(numOutboundReplicationEvents); eventsByChannel.clear(); } if (!eventKeys.isEmpty()) { _eventSource.delete(eventKeys); _eventsRead.mark(eventKeys.size()); eventKeys.clear(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy