/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.linkedin.pinot.broker.broker.helix;
import com.linkedin.pinot.common.metrics.BrokerMetrics;
import com.linkedin.pinot.common.metrics.BrokerTimer;
import com.linkedin.pinot.routing.HelixExternalViewBasedRouting;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.helix.ExternalViewChangeListener;
import org.apache.helix.InstanceConfigChangeListener;
import org.apache.helix.LiveInstanceChangeListener;
import org.apache.helix.NotificationContext;
import org.apache.helix.model.ExternalView;
import org.apache.helix.model.InstanceConfig;
import org.apache.helix.model.LiveInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages the interactions between Helix cluster changes, the routing table and the connection pool.
*/
public class ClusterChangeMediator implements LiveInstanceChangeListener, ExternalViewChangeListener, InstanceConfigChangeListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ClusterChangeMediator.class);
private final HelixExternalViewBasedRouting _helixExternalViewBasedRouting;
private enum UpdateType {
EXTERNAL_VIEW,
INSTANCE_CONFIG
}
private final LinkedBlockingQueue<Pair<UpdateType, Long>> _clusterChangeQueue = new LinkedBlockingQueue<>(1000);
private Thread _deferredClusterUpdater = null;
public ClusterChangeMediator(HelixExternalViewBasedRouting helixExternalViewBasedRouting,
final BrokerMetrics brokerMetrics) {
_helixExternalViewBasedRouting = helixExternalViewBasedRouting;
// Simple thread that polls every 10 seconds to check if there are any cluster updates to apply
_deferredClusterUpdater = new Thread("Deferred cluster state updater") {
@Override
public void run() {
while (true) {
try {
// Wait for at least one update
Pair<UpdateType, Long> firstUpdate = _clusterChangeQueue.take();
// Update the queue time metrics
long queueTime = System.currentTimeMillis() - firstUpdate.getValue();
brokerMetrics.addTimedValue(BrokerTimer.ROUTING_TABLE_UPDATE_QUEUE_TIME, queueTime, TimeUnit.MILLISECONDS);
// Take all other updates also present
List<Pair<UpdateType, Long>> allUpdates = new ArrayList<>();
allUpdates.add(firstUpdate);
_clusterChangeQueue.drainTo(allUpdates);
// Gather all update types
boolean externalViewUpdated = false;
boolean instanceConfigUpdated = false;
for (Pair<UpdateType, Long> update : allUpdates) {
if (update.getKey() == UpdateType.EXTERNAL_VIEW) {
externalViewUpdated = true;
} else if (update.getKey() == UpdateType.INSTANCE_CONFIG) {
instanceConfigUpdated = true;
}
}
if (externalViewUpdated) {
try {
_helixExternalViewBasedRouting.processExternalViewChange();
} catch (Exception e) {
LOGGER.warn("Caught exception while updating external view", e);
}
}
if (instanceConfigUpdated) {
try {
_helixExternalViewBasedRouting.processInstanceConfigChange();
} catch (Exception e) {
LOGGER.warn("Caught exception while processing instance config", e);
}
}
} catch (InterruptedException e) {
LOGGER.warn("Was interrupted while waiting for a cluster change", e);
break;
}
}
LOGGER.warn("Stopping deferred cluster state update thread");
_deferredClusterUpdater = null;
}
};
_deferredClusterUpdater.start();
}
@Override
public void onExternalViewChange(List<ExternalView> externalViewList, NotificationContext changeContext) {
// If the deferred update thread is alive, defer the update
if (_deferredClusterUpdater != null && _deferredClusterUpdater.isAlive()) {
try {
_clusterChangeQueue.put(new ImmutablePair<>(UpdateType.EXTERNAL_VIEW, System.currentTimeMillis()));
} catch (InterruptedException e) {
LOGGER.warn("Was interrupted while trying to add external view change to queue", e);
}
} else {
LOGGER.warn("Deferred cluster updater thread is null or stopped, not deferring external view routing table rebuild");
_helixExternalViewBasedRouting.processExternalViewChange();
}
}
@Override
public void onInstanceConfigChange(List<InstanceConfig> instanceConfigs, NotificationContext context) {
// If the deferred update thread is alive, defer the update
if (_deferredClusterUpdater != null && _deferredClusterUpdater.isAlive()) {
try {
_clusterChangeQueue.put(new ImmutablePair<>(UpdateType.INSTANCE_CONFIG, System.currentTimeMillis()));
} catch (InterruptedException e) {
LOGGER.warn("Was interrupted while trying to add external view change to queue", e);
}
} else {
LOGGER.warn("Deferred cluster updater thread is null or stopped, not deferring instance config change notification");
_helixExternalViewBasedRouting.processInstanceConfigChange();
}
}
@Override
public void onLiveInstanceChange(List<LiveInstance> liveInstances, NotificationContext changeContext) {
}
}