/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
http://github.com/apache/lucene-solr · Java · 2338 lines · 1574 code · 276 blank · 488 comment · 271 complexity · 271a2a5f06509d9c9e3589f165418389 MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.solr.common.cloud;
- import java.lang.invoke.MethodHandles;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.EnumSet;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.Objects;
- import java.util.Set;
- import java.util.SortedSet;
- import java.util.TreeSet;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Future;
- import java.util.concurrent.RejectedExecutionException;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.TimeoutException;
- import java.util.concurrent.atomic.AtomicBoolean;
- import java.util.concurrent.atomic.AtomicReference;
- import java.util.function.Predicate;
- import java.util.function.UnaryOperator;
- import java.util.stream.Collectors;
- import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
- import org.apache.solr.common.AlreadyClosedException;
- import org.apache.solr.common.Callable;
- import org.apache.solr.common.SolrCloseable;
- import org.apache.solr.common.SolrException;
- import org.apache.solr.common.SolrException.ErrorCode;
- import org.apache.solr.common.params.AutoScalingParams;
- import org.apache.solr.common.params.CollectionAdminParams;
- import org.apache.solr.common.params.CoreAdminParams;
- import org.apache.solr.common.util.ExecutorUtil;
- import org.apache.solr.common.util.ObjectReleaseTracker;
- import org.apache.solr.common.util.Pair;
- import org.apache.solr.common.util.SolrNamedThreadFactory;
- import org.apache.solr.common.util.Utils;
- import org.apache.zookeeper.KeeperException;
- import org.apache.zookeeper.KeeperException.NoNodeException;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.Watcher.Event.EventType;
- import org.apache.zookeeper.data.Stat;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import static java.util.Collections.EMPTY_MAP;
- import static java.util.Collections.emptyMap;
- import static java.util.Collections.emptySet;
- import static java.util.Collections.emptySortedSet;
- import static org.apache.solr.common.util.Utils.fromJSON;
- public class ZkStateReader implements SolrCloseable {
- public static final int STATE_UPDATE_DELAY = Integer.getInteger("solr.OverseerStateUpdateDelay", 2000); // delay between cloud state updates
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- public static final String BASE_URL_PROP = "base_url";
- public static final String NODE_NAME_PROP = "node_name";
- public static final String CORE_NODE_NAME_PROP = "core_node_name";
- public static final String ROLES_PROP = "roles";
- public static final String STATE_PROP = "state";
- // if this flag equals to false and the replica does not exist in cluster state, set state op become no op (default is true)
- public static final String FORCE_SET_STATE_PROP = "force_set_state";
- /**
- * SolrCore name.
- */
- public static final String CORE_NAME_PROP = "core";
- public static final String COLLECTION_PROP = "collection";
- public static final String ELECTION_NODE_PROP = "election_node";
- public static final String SHARD_ID_PROP = "shard";
- public static final String REPLICA_PROP = "replica";
- public static final String SHARD_RANGE_PROP = "shard_range";
- public static final String SHARD_STATE_PROP = "shard_state";
- public static final String SHARD_PARENT_PROP = "shard_parent";
- public static final String NUM_SHARDS_PROP = "numShards";
- public static final String LEADER_PROP = "leader";
- public static final String SHARED_STORAGE_PROP = "shared_storage";
- public static final String PROPERTY_PROP = "property";
- public static final String PROPERTY_PROP_PREFIX = "property.";
- public static final String PROPERTY_VALUE_PROP = "property.value";
- public static final String MAX_AT_ONCE_PROP = "maxAtOnce";
- public static final String MAX_WAIT_SECONDS_PROP = "maxWaitSeconds";
- public static final String STATE_TIMESTAMP_PROP = "stateTimestamp";
- public static final String COLLECTIONS_ZKNODE = "/collections";
- public static final String LIVE_NODES_ZKNODE = "/live_nodes";
- public static final String ALIASES = "/aliases.json";
- public static final String CLUSTER_STATE = "/clusterstate.json";
- public static final String CLUSTER_PROPS = "/clusterprops.json";
- public static final String COLLECTION_PROPS_ZKNODE = "collectionprops.json";
- public static final String REJOIN_AT_HEAD_PROP = "rejoinAtHead";
- public static final String SOLR_SECURITY_CONF_PATH = "/security.json";
- public static final String SOLR_AUTOSCALING_CONF_PATH = "/autoscaling.json";
- public static final String SOLR_AUTOSCALING_EVENTS_PATH = "/autoscaling/events";
- public static final String SOLR_AUTOSCALING_TRIGGER_STATE_PATH = "/autoscaling/triggerState";
- public static final String SOLR_AUTOSCALING_NODE_ADDED_PATH = "/autoscaling/nodeAdded";
- public static final String SOLR_AUTOSCALING_NODE_LOST_PATH = "/autoscaling/nodeLost";
- public static final String SOLR_PKGS_PATH = "/packages.json";
- public static final String DEFAULT_SHARD_PREFERENCES = "defaultShardPreferences";
- public static final String REPLICATION_FACTOR = "replicationFactor";
- public static final String MAX_SHARDS_PER_NODE = "maxShardsPerNode";
- public static final String AUTO_ADD_REPLICAS = "autoAddReplicas";
- public static final String MAX_CORES_PER_NODE = "maxCoresPerNode";
- public static final String PULL_REPLICAS = "pullReplicas";
- public static final String NRT_REPLICAS = "nrtReplicas";
- public static final String TLOG_REPLICAS = "tlogReplicas";
- public static final String READ_ONLY = "readOnly";
- public static final String ROLES = "/roles.json";
- public static final String CONFIGS_ZKNODE = "/configs";
- public final static String CONFIGNAME_PROP = "configName";
- public static final String LEGACY_CLOUD = "legacyCloud";
- public static final String SAMPLE_PERCENTAGE = "samplePercentage";
- /**
- * @deprecated use {@link org.apache.solr.common.params.CollectionAdminParams#DEFAULTS} instead.
- */
- @Deprecated
- public static final String COLLECTION_DEF = "collectionDefaults";
- public static final String URL_SCHEME = "urlScheme";
- private static final String SOLR_ENVIRONMENT = "environment";
- public static final String REPLICA_TYPE = "type";
- /**
- * A view of the current state of all collections; combines all the different state sources into a single view.
- */
- protected volatile ClusterState clusterState;
- private static final int GET_LEADER_RETRY_INTERVAL_MS = 50;
- private static final int GET_LEADER_RETRY_DEFAULT_TIMEOUT = Integer.parseInt(System.getProperty("zkReaderGetLeaderRetryTimeoutMs", "4000"));
- ;
- public static final String LEADER_ELECT_ZKNODE = "leader_elect";
- public static final String SHARD_LEADERS_ZKNODE = "leaders";
- public static final String ELECTION_NODE = "election";
- /**
- * Collections tracked in the legacy (shared) state format, reflects the contents of clusterstate.json.
- */
- private Map<String, ClusterState.CollectionRef> legacyCollectionStates = emptyMap();
- /**
- * Last seen ZK version of clusterstate.json.
- */
- private int legacyClusterStateVersion = 0;
- /**
- * Collections with format2 state.json, "interesting" and actively watched.
- */
- private final ConcurrentHashMap<String, DocCollection> watchedCollectionStates = new ConcurrentHashMap<>();
- /**
- * Collections with format2 state.json, not "interesting" and not actively watched.
- */
- private final ConcurrentHashMap<String, LazyCollectionRef> lazyCollectionStates = new ConcurrentHashMap<>();
- /**
- * Collection properties being actively watched
- */
- private final ConcurrentHashMap<String, VersionedCollectionProps> watchedCollectionProps = new ConcurrentHashMap<>();
- /**
- * Collection properties being actively watched
- */
- private final ConcurrentHashMap<String, PropsWatcher> collectionPropsWatchers = new ConcurrentHashMap<>();
- private volatile SortedSet<String> liveNodes = emptySortedSet();
- private volatile Map<String, Object> clusterProperties = Collections.emptyMap();
- private final ZkConfigManager configManager;
- private ConfigData securityData;
- private final Runnable securityNodeListener;
- private ConcurrentHashMap<String, CollectionWatch<DocCollectionWatcher>> collectionWatches = new ConcurrentHashMap<>();
- // named this observers so there's less confusion between CollectionPropsWatcher map and the PropsWatcher map.
- private ConcurrentHashMap<String, CollectionWatch<CollectionPropsWatcher>> collectionPropsObservers = new ConcurrentHashMap<>();
- private Set<CloudCollectionsListener> cloudCollectionsListeners = ConcurrentHashMap.newKeySet();
- private final ExecutorService notifications = ExecutorUtil.newMDCAwareCachedThreadPool("watches");
- private Set<LiveNodesListener> liveNodesListeners = ConcurrentHashMap.newKeySet();
- private Set<ClusterPropertiesListener> clusterPropertiesListeners = ConcurrentHashMap.newKeySet();
- /**
- * Used to submit notifications to Collection Properties watchers in order
- **/
- private final ExecutorService collectionPropsNotifications = ExecutorUtil.newMDCAwareSingleThreadExecutor(new SolrNamedThreadFactory("collectionPropsNotifications"));
- private static final long LAZY_CACHE_TIME = TimeUnit.NANOSECONDS.convert(STATE_UPDATE_DELAY, TimeUnit.MILLISECONDS);
- private Future<?> collectionPropsCacheCleaner; // only kept to identify if the cleaner has already been started.
- /**
- * Get current {@link AutoScalingConfig}.
- *
- * @return current configuration from <code>autoscaling.json</code>. NOTE:
- * this data is retrieved from ZK on each call.
- */
- public AutoScalingConfig getAutoScalingConfig() throws KeeperException, InterruptedException {
- return getAutoScalingConfig(null);
- }
- /**
- * Get current {@link AutoScalingConfig}.
- *
- * @param watcher optional {@link Watcher} to set on a znode to watch for config changes.
- * @return current configuration from <code>autoscaling.json</code>. NOTE:
- * this data is retrieved from ZK on each call.
- */
- public AutoScalingConfig getAutoScalingConfig(Watcher watcher) throws KeeperException, InterruptedException {
- Stat stat = new Stat();
- Map<String, Object> map = new HashMap<>();
- try {
- byte[] bytes = zkClient.getData(SOLR_AUTOSCALING_CONF_PATH, watcher, stat, true);
- if (bytes != null && bytes.length > 0) {
- map = (Map<String, Object>) fromJSON(bytes);
- }
- } catch (KeeperException.NoNodeException e) {
- // ignore
- }
- map.put(AutoScalingParams.ZK_VERSION, stat.getVersion());
- return new AutoScalingConfig(map);
- }
- private static class CollectionWatch<T> {
- int coreRefCount = 0;
- Set<T> stateWatchers = ConcurrentHashMap.newKeySet();
- public boolean canBeRemoved() {
- return coreRefCount + stateWatchers.size() == 0;
- }
- }
- public static final Set<String> KNOWN_CLUSTER_PROPS = Set.of(
- LEGACY_CLOUD,
- URL_SCHEME,
- AUTO_ADD_REPLICAS,
- CoreAdminParams.BACKUP_LOCATION,
- DEFAULT_SHARD_PREFERENCES,
- MAX_CORES_PER_NODE,
- SAMPLE_PERCENTAGE,
- SOLR_ENVIRONMENT,
- CollectionAdminParams.DEFAULTS);
- /**
- * Returns config set name for collection.
- * TODO move to DocCollection (state.json).
- *
- * @param collection to return config set name for
- */
- public String readConfigName(String collection) throws KeeperException {
- String configName = null;
- String path = COLLECTIONS_ZKNODE + "/" + collection;
- log.debug("Loading collection config from: [{}]", path);
- try {
- byte[] data = zkClient.getData(path, null, null, true);
- if (data == null) {
- log.warn("No config data found at path {}.", path);
- throw new KeeperException.NoNodeException("No config data found at path: " + path);
- }
- ZkNodeProps props = ZkNodeProps.load(data);
- configName = props.getStr(CONFIGNAME_PROP);
- if (configName == null) {
- log.warn("No config data found at path{}. ", path);
- throw new KeeperException.NoNodeException("No config data found at path: " + path);
- }
- } catch (InterruptedException e) {
- SolrZkClient.checkInterrupted(e);
- log.warn("Thread interrupted when loading config name for collection {}", collection);
- throw new SolrException(ErrorCode.SERVER_ERROR, "Thread interrupted when loading config name for collection " + collection, e);
- }
- return configName;
- }
- private final SolrZkClient zkClient;
- private final boolean closeClient;
- private volatile boolean closed = false;
- private Set<CountDownLatch> waitLatches = ConcurrentHashMap.newKeySet();
- public ZkStateReader(SolrZkClient zkClient) {
- this(zkClient, null);
- }
- public ZkStateReader(SolrZkClient zkClient, Runnable securityNodeListener) {
- this.zkClient = zkClient;
- this.configManager = new ZkConfigManager(zkClient);
- this.closeClient = false;
- this.securityNodeListener = securityNodeListener;
- assert ObjectReleaseTracker.track(this);
- }
- public ZkStateReader(String zkServerAddress, int zkClientTimeout, int zkClientConnectTimeout) {
- this.zkClient = new SolrZkClient(zkServerAddress, zkClientTimeout, zkClientConnectTimeout,
- // on reconnect, reload cloud info
- new OnReconnect() {
- @Override
- public void command() {
- try {
- ZkStateReader.this.createClusterStateWatchersAndUpdate();
- } catch (KeeperException e) {
- log.error("A ZK error has occurred", e);
- throw new ZooKeeperException(SolrException.ErrorCode.SERVER_ERROR, "A ZK error has occurred", e);
- } catch (InterruptedException e) {
- // Restore the interrupted status
- Thread.currentThread().interrupt();
- log.error("Interrupted", e);
- throw new ZooKeeperException(SolrException.ErrorCode.SERVER_ERROR, "Interrupted", e);
- }
- }
- });
- this.configManager = new ZkConfigManager(zkClient);
- this.closeClient = true;
- this.securityNodeListener = null;
- assert ObjectReleaseTracker.track(this);
- }
- public ZkConfigManager getConfigManager() {
- return configManager;
- }
- /**
- * Forcibly refresh cluster state from ZK. Do this only to avoid race conditions because it's expensive.
- * <p>
- * It is cheaper to call {@link #forceUpdateCollection(String)} on a single collection if you must.
- *
- * @lucene.internal
- */
- public void forciblyRefreshAllClusterStateSlow() throws KeeperException, InterruptedException {
- synchronized (getUpdateLock()) {
- if (clusterState == null) {
- // Never initialized, just run normal initialization.
- createClusterStateWatchersAndUpdate();
- return;
- }
- // No need to set watchers because we should already have watchers registered for everything.
- refreshCollectionList(null);
- refreshLiveNodes(null);
- refreshLegacyClusterState(null);
- // Need a copy so we don't delete from what we're iterating over.
- Collection<String> safeCopy = new ArrayList<>(watchedCollectionStates.keySet());
- Set<String> updatedCollections = new HashSet<>();
- for (String coll : safeCopy) {
- DocCollection newState = fetchCollectionState(coll, null);
- if (updateWatchedCollection(coll, newState)) {
- updatedCollections.add(coll);
- }
- }
- constructState(updatedCollections);
- }
- }
- /**
- * Forcibly refresh a collection's internal state from ZK. Try to avoid having to resort to this when
- * a better design is possible.
- */
- //TODO shouldn't we call ZooKeeper.sync() at the right places to prevent reading a stale value? We do so for aliases.
- public void forceUpdateCollection(String collection) throws KeeperException, InterruptedException {
- synchronized (getUpdateLock()) {
- if (clusterState == null) {
- log.warn("ClusterState watchers have not been initialized");
- return;
- }
- ClusterState.CollectionRef ref = clusterState.getCollectionRef(collection);
- if (ref == null || legacyCollectionStates.containsKey(collection)) {
- // We either don't know anything about this collection (maybe it's new?) or it's legacy.
- // First update the legacy cluster state.
- log.debug("Checking legacy cluster state for collection {}", collection);
- refreshLegacyClusterState(null);
- if (!legacyCollectionStates.containsKey(collection)) {
- // No dice, see if a new collection just got created.
- LazyCollectionRef tryLazyCollection = new LazyCollectionRef(collection);
- if (tryLazyCollection.get() != null) {
- // What do you know, it exists!
- log.debug("Adding lazily-loaded reference for collection {}", collection);
- lazyCollectionStates.putIfAbsent(collection, tryLazyCollection);
- constructState(Collections.singleton(collection));
- }
- }
- } else if (ref.isLazilyLoaded()) {
- log.debug("Refreshing lazily-loaded state for collection {}", collection);
- if (ref.get() != null) {
- return;
- }
- // Edge case: if there's no external collection, try refreshing legacy cluster state in case it's there.
- refreshLegacyClusterState(null);
- } else if (watchedCollectionStates.containsKey(collection)) {
- // Exists as a watched collection, force a refresh.
- log.debug("Forcing refresh of watched collection state for {}", collection);
- DocCollection newState = fetchCollectionState(collection, null);
- if (updateWatchedCollection(collection, newState)) {
- constructState(Collections.singleton(collection));
- }
- } else {
- log.error("Collection {} is not lazy or watched!", collection);
- }
- }
- }
- /**
- * Refresh the set of live nodes.
- */
- public void updateLiveNodes() throws KeeperException, InterruptedException {
- refreshLiveNodes(null);
- }
- public Integer compareStateVersions(String coll, int version) {
- DocCollection collection = clusterState.getCollectionOrNull(coll);
- if (collection == null) return null;
- if (collection.getZNodeVersion() < version) {
- if (log.isDebugEnabled()) {
- log.debug("Server older than client {}<{}", collection.getZNodeVersion(), version);
- }
- DocCollection nu = getCollectionLive(this, coll);
- if (nu == null) return -1;
- if (nu.getZNodeVersion() > collection.getZNodeVersion()) {
- if (updateWatchedCollection(coll, nu)) {
- synchronized (getUpdateLock()) {
- constructState(Collections.singleton(coll));
- }
- }
- collection = nu;
- }
- }
- if (collection.getZNodeVersion() == version) {
- return null;
- }
- if (log.isDebugEnabled()) {
- log.debug("Wrong version from client [{}]!=[{}]", version, collection.getZNodeVersion());
- }
- return collection.getZNodeVersion();
- }
- public synchronized void createClusterStateWatchersAndUpdate() throws KeeperException,
- InterruptedException {
- // We need to fetch the current cluster state and the set of live nodes
- log.debug("Updating cluster state from ZooKeeper... ");
- // Sanity check ZK structure.
- if (!zkClient.exists(CLUSTER_STATE, true)) {
- throw new SolrException(ErrorCode.SERVICE_UNAVAILABLE,
- "Cannot connect to cluster at " + zkClient.getZkServerAddress() + ": cluster not found/not ready");
- }
- // on reconnect of SolrZkClient force refresh and re-add watches.
- loadClusterProperties();
- refreshLiveNodes(new LiveNodeWatcher());
- refreshLegacyClusterState(new LegacyClusterStateWatcher());
- refreshStateFormat2Collections();
- refreshCollectionList(new CollectionsChildWatcher());
- refreshAliases(aliasesManager);
- if (securityNodeListener != null) {
- addSecurityNodeWatcher(pair -> {
- ConfigData cd = new ConfigData();
- cd.data = pair.first() == null || pair.first().length == 0 ? EMPTY_MAP : Utils.getDeepCopy((Map) fromJSON(pair.first()), 4, false);
- cd.version = pair.second() == null ? -1 : pair.second().getVersion();
- securityData = cd;
- securityNodeListener.run();
- });
- securityData = getSecurityProps(true);
- }
- collectionPropsObservers.forEach((k, v) -> {
- collectionPropsWatchers.computeIfAbsent(k, PropsWatcher::new).refreshAndWatch(true);
- });
- }
- private void addSecurityNodeWatcher(final Callable<Pair<byte[], Stat>> callback)
- throws KeeperException, InterruptedException {
- zkClient.exists(SOLR_SECURITY_CONF_PATH,
- new Watcher() {
- @Override
- public void process(WatchedEvent event) {
- // session events are not change events, and do not remove the watcher
- if (EventType.None.equals(event.getType())) {
- return;
- }
- try {
- synchronized (ZkStateReader.this.getUpdateLock()) {
- log.debug("Updating [{}] ... ", SOLR_SECURITY_CONF_PATH);
- // remake watch
- final Watcher thisWatch = this;
- final Stat stat = new Stat();
- final byte[] data = getZkClient().getData(SOLR_SECURITY_CONF_PATH, thisWatch, stat, true);
- try {
- callback.call(new Pair<>(data, stat));
- } catch (Exception e) {
- log.error("Error running collections node listener", e);
- }
- }
- } catch (KeeperException.ConnectionLossException | KeeperException.SessionExpiredException e) {
- log.warn("ZooKeeper watch triggered, but Solr cannot talk to ZK: [{}]", e.getMessage());
- } catch (KeeperException e) {
- log.error("A ZK error has occurred", e);
- throw new ZooKeeperException(ErrorCode.SERVER_ERROR, "", e);
- } catch (InterruptedException e) {
- // Restore the interrupted status
- Thread.currentThread().interrupt();
- log.warn("Interrupted", e);
- }
- }
- }, true);
- }
- /**
- * Construct the total state view from all sources.
- * Must hold {@link #getUpdateLock()} before calling this.
- *
- * @param changedCollections collections that have changed since the last call,
- * and that should fire notifications
- */
- private void constructState(Set<String> changedCollections) {
- Set<String> liveNodes = this.liveNodes; // volatile read
- // Legacy clusterstate is authoritative, for backwards compatibility.
- // To move a collection's state to format2, first create the new state2 format node, then remove legacy entry.
- Map<String, ClusterState.CollectionRef> result = new LinkedHashMap<>(legacyCollectionStates);
- // Add state format2 collections, but don't override legacy collection states.
- for (Map.Entry<String, DocCollection> entry : watchedCollectionStates.entrySet()) {
- result.putIfAbsent(entry.getKey(), new ClusterState.CollectionRef(entry.getValue()));
- }
- // Finally, add any lazy collections that aren't already accounted for.
- for (Map.Entry<String, LazyCollectionRef> entry : lazyCollectionStates.entrySet()) {
- result.putIfAbsent(entry.getKey(), entry.getValue());
- }
- this.clusterState = new ClusterState(liveNodes, result, legacyClusterStateVersion);
- if (log.isDebugEnabled()) {
- log.debug("clusterStateSet: legacy [{}] interesting [{}] watched [{}] lazy [{}] total [{}]",
- legacyCollectionStates.keySet().size(),
- collectionWatches.keySet().size(),
- watchedCollectionStates.keySet().size(),
- lazyCollectionStates.keySet().size(),
- clusterState.getCollectionStates().size());
- }
- if (log.isTraceEnabled()) {
- log.trace("clusterStateSet: legacy [{}] interesting [{}] watched [{}] lazy [{}] total [{}]",
- legacyCollectionStates.keySet(),
- collectionWatches.keySet(),
- watchedCollectionStates.keySet(),
- lazyCollectionStates.keySet(),
- clusterState.getCollectionStates());
- }
- notifyCloudCollectionsListeners();
- for (String collection : changedCollections) {
- notifyStateWatchers(collection, clusterState.getCollectionOrNull(collection));
- }
- }
- /**
- * Refresh legacy (shared) clusterstate.json
- */
- private void refreshLegacyClusterState(Watcher watcher) throws KeeperException, InterruptedException {
- try {
- final Stat stat = new Stat();
- final byte[] data = zkClient.getData(CLUSTER_STATE, watcher, stat, true);
- final ClusterState loadedData = ClusterState.load(stat.getVersion(), data, emptySet(), CLUSTER_STATE);
- synchronized (getUpdateLock()) {
- if (this.legacyClusterStateVersion >= stat.getVersion()) {
- // Nothing to do, someone else updated same or newer.
- return;
- }
- Set<String> updatedCollections = new HashSet<>();
- for (String coll : this.collectionWatches.keySet()) {
- ClusterState.CollectionRef ref = this.legacyCollectionStates.get(coll);
- // legacy collections are always in-memory
- DocCollection oldState = ref == null ? null : ref.get();
- ClusterState.CollectionRef newRef = loadedData.getCollectionStates().get(coll);
- DocCollection newState = newRef == null ? null : newRef.get();
- if (newState == null) {
- // check that we haven't just migrated
- newState = watchedCollectionStates.get(coll);
- }
- if (!Objects.equals(oldState, newState)) {
- updatedCollections.add(coll);
- }
- }
- this.legacyCollectionStates = loadedData.getCollectionStates();
- this.legacyClusterStateVersion = stat.getVersion();
- constructState(updatedCollections);
- }
- } catch (KeeperException.NoNodeException e) {
- // Ignore missing legacy clusterstate.json.
- synchronized (getUpdateLock()) {
- this.legacyCollectionStates = emptyMap();
- this.legacyClusterStateVersion = 0;
- constructState(Collections.emptySet());
- }
- }
- }
- /**
- * Refresh state format2 collections.
- */
- private void refreshStateFormat2Collections() {
- for (String coll : collectionWatches.keySet()) {
- new StateWatcher(coll).refreshAndWatch();
- }
- }
- // We don't get a Stat or track versions on getChildren() calls, so force linearization.
- private final Object refreshCollectionListLock = new Object();
- /**
- * Search for any lazy-loadable state format2 collections.
- * <p>
- * A stateFormat=1 collection which is not interesting to us can also
- * be put into the {@link #lazyCollectionStates} map here. But that is okay
- * because {@link #constructState(Set)} will give priority to collections in the
- * shared collection state over this map.
- * In fact this is a clever way to avoid doing a ZK exists check on
- * the /collections/collection_name/state.json znode
- * Such an exists check is done in {@link ClusterState#hasCollection(String)} and
- * {@link ClusterState#getCollectionsMap()} methods
- * have a safeguard against exposing wrong collection names to the users
- */
- private void refreshCollectionList(Watcher watcher) throws KeeperException, InterruptedException {
- synchronized (refreshCollectionListLock) {
- List<String> children = null;
- try {
- children = zkClient.getChildren(COLLECTIONS_ZKNODE, watcher, true);
- } catch (KeeperException.NoNodeException e) {
- log.warn("Error fetching collection names: [{}]", e.getMessage());
- // fall through
- }
- if (children == null || children.isEmpty()) {
- lazyCollectionStates.clear();
- return;
- }
- // Don't lock getUpdateLock() here, we don't need it and it would cause deadlock.
- // Don't mess with watchedCollections, they should self-manage.
- // First, drop any children that disappeared.
- this.lazyCollectionStates.keySet().retainAll(children);
- for (String coll : children) {
- // We will create an eager collection for any interesting collections, so don't add to lazy.
- if (!collectionWatches.containsKey(coll)) {
- // Double check contains just to avoid allocating an object.
- LazyCollectionRef existing = lazyCollectionStates.get(coll);
- if (existing == null) {
- lazyCollectionStates.putIfAbsent(coll, new LazyCollectionRef(coll));
- }
- }
- }
- }
- }
- // We don't get a Stat or track versions on getChildren() calls, so force linearization.
- private final Object refreshCollectionsSetLock = new Object();
- // Ensures that only the latest getChildren fetch gets applied.
- private final AtomicReference<Set<String>> lastFetchedCollectionSet = new AtomicReference<>();
- /**
- * Register a CloudCollectionsListener to be called when the set of collections within a cloud changes.
- */
- public void registerCloudCollectionsListener(CloudCollectionsListener cloudCollectionsListener) {
- cloudCollectionsListeners.add(cloudCollectionsListener);
- notifyNewCloudCollectionsListener(cloudCollectionsListener);
- }
- /**
- * Remove a registered CloudCollectionsListener.
- */
- public void removeCloudCollectionsListener(CloudCollectionsListener cloudCollectionsListener) {
- cloudCollectionsListeners.remove(cloudCollectionsListener);
- }
- private void notifyNewCloudCollectionsListener(CloudCollectionsListener listener) {
- listener.onChange(Collections.emptySet(), lastFetchedCollectionSet.get());
- }
- private void notifyCloudCollectionsListeners() {
- notifyCloudCollectionsListeners(false);
- }
- private void notifyCloudCollectionsListeners(boolean notifyIfSame) {
- synchronized (refreshCollectionsSetLock) {
- final Set<String> newCollections = getCurrentCollections();
- final Set<String> oldCollections = lastFetchedCollectionSet.getAndSet(newCollections);
- if (!newCollections.equals(oldCollections) || notifyIfSame) {
- cloudCollectionsListeners.forEach(listener -> listener.onChange(oldCollections, newCollections));
- }
- }
- }
- private Set<String> getCurrentCollections() {
- Set<String> collections = new HashSet<>();
- collections.addAll(legacyCollectionStates.keySet());
- collections.addAll(watchedCollectionStates.keySet());
- collections.addAll(lazyCollectionStates.keySet());
- return collections;
- }
- private class LazyCollectionRef extends ClusterState.CollectionRef {
- private final String collName;
- private long lastUpdateTime;
- private DocCollection cachedDocCollection;
- public LazyCollectionRef(String collName) {
- super(null);
- this.collName = collName;
- this.lastUpdateTime = -1;
- }
- @Override
- public synchronized DocCollection get(boolean allowCached) {
- gets.incrementAndGet();
- if (!allowCached || lastUpdateTime < 0 || System.nanoTime() - lastUpdateTime > LAZY_CACHE_TIME) {
- boolean shouldFetch = true;
- if (cachedDocCollection != null) {
- Stat exists = null;
- try {
- exists = zkClient.exists(getCollectionPath(collName), null, true);
- } catch (Exception e) {
- }
- if (exists != null && exists.getVersion() == cachedDocCollection.getZNodeVersion()) {
- shouldFetch = false;
- }
- }
- if (shouldFetch) {
- cachedDocCollection = getCollectionLive(ZkStateReader.this, collName);
- lastUpdateTime = System.nanoTime();
- }
- }
- return cachedDocCollection;
- }
- @Override
- public boolean isLazilyLoaded() {
- return true;
- }
- @Override
- public String toString() {
- return "LazyCollectionRef(" + collName + ")";
- }
- }
- // We don't get a Stat or track versions on getChildren() calls, so force linearization.
- private final Object refreshLiveNodesLock = new Object();
- // Ensures that only the latest getChildren fetch gets applied.
- private final AtomicReference<SortedSet<String>> lastFetchedLiveNodes = new AtomicReference<>();
- /**
- * Refresh live_nodes.
- */
- private void refreshLiveNodes(Watcher watcher) throws KeeperException, InterruptedException {
- synchronized (refreshLiveNodesLock) {
- SortedSet<String> newLiveNodes;
- try {
- List<String> nodeList = zkClient.getChildren(LIVE_NODES_ZKNODE, watcher, true);
- newLiveNodes = new TreeSet<>(nodeList);
- } catch (KeeperException.NoNodeException e) {
- newLiveNodes = emptySortedSet();
- }
- lastFetchedLiveNodes.set(newLiveNodes);
- }
- // Can't lock getUpdateLock() until we release the other, it would cause deadlock.
- SortedSet<String> oldLiveNodes, newLiveNodes;
- synchronized (getUpdateLock()) {
- newLiveNodes = lastFetchedLiveNodes.getAndSet(null);
- if (newLiveNodes == null) {
- // Someone else won the race to apply the last update, just exit.
- return;
- }
- oldLiveNodes = this.liveNodes;
- this.liveNodes = newLiveNodes;
- if (clusterState != null) {
- clusterState.setLiveNodes(newLiveNodes);
- }
- }
- if (oldLiveNodes.size() != newLiveNodes.size()) {
- if (log.isInfoEnabled()) {
- log.info("Updated live nodes from ZooKeeper... ({}) -> ({})", oldLiveNodes.size(), newLiveNodes.size());
- }
- }
- if (log.isDebugEnabled()) {
- log.debug("Updated live nodes from ZooKeeper... {} -> {}", oldLiveNodes, newLiveNodes);
- }
- if (!oldLiveNodes.equals(newLiveNodes)) { // fire listeners
- liveNodesListeners.forEach(listener -> {
- if (listener.onChange(new TreeSet<>(oldLiveNodes), new TreeSet<>(newLiveNodes))) {
- removeLiveNodesListener(listener);
- }
- });
- }
- }
- public void registerClusterPropertiesListener(ClusterPropertiesListener listener) {
- // fire it once with current properties
- if (listener.onChange(getClusterProperties())) {
- removeClusterPropertiesListener(listener);
- } else {
- clusterPropertiesListeners.add(listener);
- }
- }
- public void removeClusterPropertiesListener(ClusterPropertiesListener listener) {
- clusterPropertiesListeners.remove(listener);
- }
- public void registerLiveNodesListener(LiveNodesListener listener) {
- // fire it once with current live nodes
- if (listener.onChange(new TreeSet<>(getClusterState().getLiveNodes()), new TreeSet<>(getClusterState().getLiveNodes()))) {
- removeLiveNodesListener(listener);
- }
- liveNodesListeners.add(listener);
- }
- public void removeLiveNodesListener(LiveNodesListener listener) {
- liveNodesListeners.remove(listener);
- }
- /**
- * @return information about the cluster from ZooKeeper
- */
- public ClusterState getClusterState() {
- return clusterState;
- }
- public Object getUpdateLock() {
- return this;
- }
- public void close() {
- this.closed = true;
- notifications.shutdownNow();
- waitLatches.parallelStream().forEach(c -> {
- c.countDown();
- });
- ExecutorUtil.shutdownAndAwaitTermination(notifications);
- ExecutorUtil.shutdownAndAwaitTermination(collectionPropsNotifications);
- if (closeClient) {
- zkClient.close();
- }
- assert ObjectReleaseTracker.release(this);
- }
- @Override
- public boolean isClosed() {
- return closed;
- }
- public String getLeaderUrl(String collection, String shard, int timeout) throws InterruptedException {
- ZkCoreNodeProps props = new ZkCoreNodeProps(getLeaderRetry(collection, shard, timeout));
- return props.getCoreUrl();
- }
- public Replica getLeader(Set<String> liveNodes, DocCollection docCollection, String shard) {
- Replica replica = docCollection != null ? docCollection.getLeader(shard) : null;
- if (replica != null && liveNodes.contains(replica.getNodeName())) {
- return replica;
- }
- return null;
- }
- public Replica getLeader(String collection, String shard) {
- if (clusterState != null) {
- DocCollection docCollection = clusterState.getCollectionOrNull(collection);
- Replica replica = docCollection != null ? docCollection.getLeader(shard) : null;
- if (replica != null && getClusterState().liveNodesContain(replica.getNodeName())) {
- return replica;
- }
- }
- return null;
- }
- public boolean isNodeLive(String node) {
- return liveNodes.contains(node);
- }
- /**
- * Get shard leader properties, with retry if none exist.
- */
- public Replica getLeaderRetry(String collection, String shard) throws InterruptedException {
- return getLeaderRetry(collection, shard, GET_LEADER_RETRY_DEFAULT_TIMEOUT);
- }
- /**
- * Get shard leader properties, with retry if none exist.
- */
- public Replica getLeaderRetry(String collection, String shard, int timeout) throws InterruptedException {
- AtomicReference<Replica> leader = new AtomicReference<>();
- try {
- waitForState(collection, timeout, TimeUnit.MILLISECONDS, (n, c) -> {
- if (c == null)
- return false;
- Replica l = getLeader(n, c, shard);
- if (l != null) {
- leader.set(l);
- return true;
- }
- return false;
- });
- } catch (TimeoutException e) {
- throw new SolrException(ErrorCode.SERVICE_UNAVAILABLE, "No registered leader was found after waiting for "
- + timeout + "ms " + ", collection: " + collection + " slice: " + shard + " saw state=" + clusterState.getCollectionOrNull(collection)
- + " with live_nodes=" + clusterState.getLiveNodes());
- }
- return leader.get();
- }
- /**
- * Get path where shard leader properties live in zookeeper.
- */
- public static String getShardLeadersPath(String collection, String shardId) {
- return COLLECTIONS_ZKNODE + "/" + collection + "/"
- + SHARD_LEADERS_ZKNODE + (shardId != null ? ("/" + shardId)
- : "") + "/leader";
- }
- /**
- * Get path where shard leader elections ephemeral nodes are.
- */
- public static String getShardLeadersElectPath(String collection, String shardId) {
- return COLLECTIONS_ZKNODE + "/" + collection + "/"
- + LEADER_ELECT_ZKNODE + (shardId != null ? ("/" + shardId + "/" + ELECTION_NODE)
- : "");
- }
- public List<ZkCoreNodeProps> getReplicaProps(String collection, String shardId, String thisCoreNodeName) {
- return getReplicaProps(collection, shardId, thisCoreNodeName, null);
- }
- public List<ZkCoreNodeProps> getReplicaProps(String collection, String shardId, String thisCoreNodeName,
- Replica.State mustMatchStateFilter) {
- return getReplicaProps(collection, shardId, thisCoreNodeName, mustMatchStateFilter, null);
- }
- public List<ZkCoreNodeProps> getReplicaProps(String collection, String shardId, String thisCoreNodeName,
- Replica.State mustMatchStateFilter, Replica.State mustNotMatchStateFilter) {
- //TODO: We don't need all these getReplicaProps method overloading. Also, it's odd that the default is to return replicas of type TLOG and NRT only
- return getReplicaProps(collection, shardId, thisCoreNodeName, mustMatchStateFilter, null, EnumSet.of(Replica.Type.TLOG, Replica.Type.NRT));
- }
- public List<ZkCoreNodeProps> getReplicaProps(String collection, String shardId, String thisCoreNodeName,
- Replica.State mustMatchStateFilter, Replica.State mustNotMatchStateFilter, final EnumSet<Replica.Type> acceptReplicaType) {
- assert thisCoreNodeName != null;
- ClusterState clusterState = this.clusterState;
- if (clusterState == null) {
- return null;
- }
- final DocCollection docCollection = clusterState.getCollectionOrNull(collection);
- if (docCollection == null || docCollection.getSlicesMap() == null) {
- throw new ZooKeeperException(ErrorCode.BAD_REQUEST,
- "Could not find collection in zk: " + collection);
- }
- Map<String, Slice> slices = docCollection.getSlicesMap();
- Slice replicas = slices.get(shardId);
- if (replicas == null) {
- throw new ZooKeeperException(ErrorCode.BAD_REQUEST, "Could not find shardId in zk: " + shardId);
- }
- Map<String, Replica> shardMap = replicas.getReplicasMap();
- List<ZkCoreNodeProps> nodes = new ArrayList<>(shardMap.size());
- for (Entry<String, Replica> entry : shardMap.entrySet().stream().filter((e) -> acceptReplicaType.contains(e.getValue().getType())).collect(Collectors.toList())) {
- ZkCoreNodeProps nodeProps = new ZkCoreNodeProps(entry.getValue());
- String coreNodeName = entry.getValue().getName();
- if (clusterState.liveNodesContain(nodeProps.getNodeName()) && !coreNodeName.equals(thisCoreNodeName)) {
- if (mustMatchStateFilter == null || mustMatchStateFilter == Replica.State.getState(nodeProps.getState())) {
- if (mustNotMatchStateFilter == null || mustNotMatchStateFilter != Replica.State.getState(nodeProps.getState())) {
- nodes.add(nodeProps);
- }
- }
- }
- }
- if (nodes.size() == 0) {
- // no replicas
- return null;
- }
- return nodes;
- }
- public SolrZkClient getZkClient() {
- return zkClient;
- }
- /**
- * Get a cluster property
- * <p>
- * N.B. Cluster properties are updated via ZK watchers, and so may not necessarily
- * be completely up-to-date. If you need to get the latest version, then use a
- * {@link ClusterProperties} instance.
- *
- * @param key the property to read
- * @param defaultValue a default value to use if no such property exists
- * @param <T> the type of the property
- * @return the cluster property, or a default if the property is not set
- */
- @SuppressWarnings("unchecked")
- public <T> T getClusterProperty(String key, T defaultValue) {
- T value = (T) Utils.getObjectByPath(clusterProperties, false, key);
- if (value == null)
- return defaultValue;
- return value;
- }
- /**
- * Same as the above but allows a full json path as a list of parts
- *
- * @param keyPath path to the property example ["collectionDefauls", "numShards"]
- * @param defaultValue a default value to use if no such property exists
- * @return the cluster property, or a default if the property is not set
- */
- public <T> T getClusterProperty(List<String> keyPath, T defaultValue) {
- T value = (T) Utils.getObjectByPath(clusterProperties, false, keyPath);
- if (value == null)
- return defaultValue;
- return value;
- }
- /**
- * Get all cluster properties for this cluster
- * <p>
- * N.B. Cluster properties are updated via ZK watchers, and so may not necessarily
- * be completely up-to-date. If you need to get the latest version, then use a
- * {@link ClusterProperties} instance.
- *
- * @return a Map of cluster properties
- */
- public Map<String, Object> getClusterProperties() {
- return Collections.unmodifiableMap(clusterProperties);
- }
- private final Watcher clusterPropertiesWatcher = event -> {
- // session events are not change events, and do not remove the watcher
- if (Watcher.Event.EventType.None.equals(event.getType())) {
- return;
- }
- loadClusterProperties();
- };
- @SuppressWarnings("unchecked")
- private void loadClusterProperties() {
- try {
- while (true) {
- try {
- byte[] data = zkClient.getData(ZkStateReader.CLUSTER_PROPS, clusterPropertiesWatcher, new Stat(), true);
- this.clusterProperties = ClusterProperties.convertCollectionDefaultsToNestedFormat((Map<String, Object>) Utils.fromJSON(data));
- log.debug("Loaded cluster properties: {}", this.clusterProperties);
- for (ClusterPropertiesListener listener : clusterPropertiesListeners) {
- listener.onChange(getClusterProperties());
- }
- return;
- } catch (KeeperException.NoNodeException e) {
- this.clusterProperties = Collections.emptyMap();
- log.debug("Loaded empty cluster properties");
- // set an exists watch, and if the node has been created since the last call,
- // read the data again
- if (zkClient.exists(ZkStateReader.CLUSTER_PROPS, clusterPropertiesWatcher, true) == null)
- return;
- }
- }
- } catch (KeeperException | InterruptedException e) {
- log.error("Error reading cluster properties from zookeeper", SolrZkClient.checkInterrupted(e));
- }
- }
- /**
- * Get collection properties for a given collection. If the collection is watched, simply return it from the cache,
- * otherwise fetch it directly from zookeeper. This is a convenience for {@code getCollectionProperties(collection,0)}
- *
- * @param collection the collection for which properties are desired
- * @return a map representing the key/value properties for the collection.
- */
- public Map<String, String> getCollectionProperties(final String collection) {
- return getCollectionProperties(collection, 0);
- }
- /**
- * Get and cache collection properties for a given collection. If the collection is watched, or still cached
- * simply return it from the cache, otherwise fetch it directly from zookeeper and retain the value for at
- * least cacheForMillis milliseconds. Cached properties are watched in zookeeper and updated automatically.
- * This version of {@code getCollectionProperties} should be used when properties need to be consulted
- * frequently in the absence of an active {@link CollectionPropsWatcher}.
- *
- * @param collection The collection for which properties are desired
- * @param cacheForMillis The minimum number of milliseconds to maintain a cache for the specified collection's
- * properties. Setting a {@code CollectionPropsWatcher} will override this value and retain
- * the cache for the life of the watcher. A lack of changes in zookeeper may allow the
- * caching to remain for a greater duration up to the cycle time of {@link CacheCleaner}.
- * Passing zero for this value will explicitly remove the cached copy if and only if it is
- * due to expire and no watch exists. Any positive value will extend the expiration time
- * if required.
- * @return a map representing the key/value properties for the collection.
- */
- public Map<String, String> getCollectionProperties(final String collection, long cacheForMillis) {
- synchronized (watchedCollectionProps) { // making decisions based on the result of a get...
- Watcher watcher = null;
- if (cacheForMillis > 0) {
- watcher = collectionPropsWatchers.compute(collection,
- (c, w) -> w == null ? new PropsWatcher(c, cacheForMillis) : w.renew(cacheForMillis));
- }
- VersionedCollectionProps vprops = watchedCollectionProps.get(collection);
- boolean haveUnexpiredProps = vprops != null && vprops.cacheUntilNs > System.nanoTime();
- long untilNs = System.nanoTime() + TimeUnit.NANOSECONDS.convert(cacheForMillis, TimeUnit.MILLISECONDS);
- Map<String, String> properties;
- if (haveUnexpiredProps) {
- properties = vprops.props;
- vprops.cacheUntilNs = Math.max(vprops.cacheUntilNs, untilNs);
- } else {
- try {
- VersionedCollectionProps vcp = fetchCollectionProperties(collection, watcher);
- properties = vcp.props;
- if (cacheForMillis > 0) {
- vcp.cacheUntilNs = untilNs;
- watchedCollectionProps.put(collection, vcp);
- } else {
- // we're synchronized on watchedCollectionProps and we can only get here if we have found an expired
- // vprops above, so it is safe to remove the cached value and let the GC free up some mem a bit sooner.
- if (!collectionPropsObservers.containsKey(collection)) {
- watchedCollectionProps.remove(collection);
- }
- }
- } catch (Exception e) {
- throw new SolrException(ErrorCode.SERVER_ERROR, "Error reading collection properties", SolrZkClient.checkInterrupted(e));
- }
- }
- return properties;
- }
- }
- private class VersionedCollectionProps {
- int zkVersion;
- Map<String, String> props;
- long cacheUntilNs = 0;
- VersionedCollectionProps(int zkVersion, Map<String, String> props) {
- this.zkVersion = zkVersion;
- this.props = props;
- }
- }
- static String getCollectionPropsPath(final String collection) {
- return COLLECTIONS_ZKNODE + '/' + collection + '/' + COLLECTION_PROPS_ZKNODE;
- }
- @SuppressWarnings("unchecked")
- private VersionedCollectionProps fetchCollectionProperties(String collection, Watcher watcher) throws KeeperException, InterruptedException {
- final String znodePath = getCollectionPropsPath(collection);
- // lazy init cache cleaner once we know someone is using collection properties.
- if (collectionPropsCacheCl…