You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by kr...@apache.org on 2016/11/10 21:16:00 UTC
[30/36] lucene-solr:jira/solr-8593: SOLR-8332: Factor
HttpShardHandler[Factory]'s url shuffling out into a ReplicaListTransformer
class. (Christine Poerschke, Noble Paul)
SOLR-8332: Factor HttpShardHandler[Factory]'s url shuffling out into a ReplicaListTransformer class.
(Christine Poerschke, Noble Paul)
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/6c25adb1
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/6c25adb1
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/6c25adb1
Branch: refs/heads/jira/solr-8593
Commit: 6c25adb11963c9859bb1ccd15e7afdb171c85f48
Parents: 98b8370
Author: Christine Poerschke <cp...@apache.org>
Authored: Wed Nov 9 09:46:14 2016 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Wed Nov 9 09:46:14 2016 +0000
----------------------------------------------------------------------
solr/CHANGES.txt | 3 +
.../handler/component/HttpShardHandler.java | 50 ++++--
.../component/HttpShardHandlerFactory.java | 21 ++-
.../component/ReplicaListTransformer.java | 35 ++++
.../ShufflingReplicaListTransformer.java | 39 +++++
.../component/ReplicaListTransformerTest.java | 163 +++++++++++++++++++
.../ShufflingReplicaListTransformerTest.java | 76 +++++++++
7 files changed, 359 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index d2aeadb..efd1c94 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -156,6 +156,9 @@ Other Changes
* SOLR-9739: JavabinCodec implements PushWriter interface (noble)
+* SOLR-8332: Factor HttpShardHandler[Factory]'s url shuffling out into a ReplicaListTransformer class.
+ (Christine Poerschke, Noble Paul)
+
================== 6.3.0 ==================
Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
index 609e433..6a55a0d 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
@@ -17,6 +17,7 @@
package org.apache.solr.handler.component;
import java.lang.invoke.MethodHandles;
import java.net.ConnectException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -116,7 +117,7 @@ public class HttpShardHandler extends ShardHandler {
private List<String> getURLs(String shard, String preferredHostAddress) {
List<String> urls = shardToURLs.get(shard);
if (urls == null) {
- urls = httpShardHandlerFactory.makeURLList(shard);
+ urls = httpShardHandlerFactory.buildURLList(shard);
if (preferredHostAddress != null && urls.size() > 1) {
preferCurrentHostForDistributedReq(preferredHostAddress, urls);
}
@@ -320,6 +321,8 @@ public class HttpShardHandler extends ShardHandler {
}
}
+ final ReplicaListTransformer replicaListTransformer = httpShardHandlerFactory.getReplicaListTransformer(req);
+
if (shards != null) {
List<String> lst = StrUtils.splitSmart(shards, ",", true);
rb.shards = lst.toArray(new String[lst.size()]);
@@ -404,7 +407,11 @@ public class HttpShardHandler extends ShardHandler {
for (int i=0; i<rb.shards.length; i++) {
- if (rb.shards[i] == null) {
+ final List<String> shardUrls;
+ if (rb.shards[i] != null) {
+ shardUrls = StrUtils.splitSmart(rb.shards[i], "|", true);
+ replicaListTransformer.transform(shardUrls);
+ } else {
if (clusterState == null) {
clusterState = zkController.getClusterState();
slices = clusterState.getSlicesMap(cloudDescriptor.getCollectionName());
@@ -421,26 +428,25 @@ public class HttpShardHandler extends ShardHandler {
// throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "no such shard: " + sliceName);
}
- Map<String, Replica> sliceShards = slice.getReplicasMap();
-
- // For now, recreate the | delimited list of equivalent servers
- StringBuilder sliceShardsStr = new StringBuilder();
- boolean first = true;
- for (Replica replica : sliceShards.values()) {
+ final Collection<Replica> allSliceReplicas = slice.getReplicasMap().values();
+ final List<Replica> eligibleSliceReplicas = new ArrayList<>(allSliceReplicas.size());
+ for (Replica replica : allSliceReplicas) {
if (!clusterState.liveNodesContain(replica.getNodeName())
|| replica.getState() != Replica.State.ACTIVE) {
continue;
}
- if (first) {
- first = false;
- } else {
- sliceShardsStr.append('|');
- }
+ eligibleSliceReplicas.add(replica);
+ }
+
+ replicaListTransformer.transform(eligibleSliceReplicas);
+
+ shardUrls = new ArrayList<>(eligibleSliceReplicas.size());
+ for (Replica replica : eligibleSliceReplicas) {
String url = ZkCoreNodeProps.getCoreUrl(replica);
- sliceShardsStr.append(url);
+ shardUrls.add(url);
}
- if (sliceShardsStr.length() == 0) {
+ if (shardUrls.isEmpty()) {
boolean tolerant = rb.req.getParams().getBool(ShardParams.SHARDS_TOLERANT, false);
if (!tolerant) {
// stop the check when there are no replicas available for a shard
@@ -448,9 +454,19 @@ public class HttpShardHandler extends ShardHandler {
"no servers hosting shard: " + rb.slices[i]);
}
}
-
- rb.shards[i] = sliceShardsStr.toString();
}
+ // And now recreate the | delimited list of equivalent servers
+ final StringBuilder sliceShardsStr = new StringBuilder();
+ boolean first = true;
+ for (String shardUrl : shardUrls) {
+ if (first) {
+ first = false;
+ } else {
+ sliceShardsStr.append('|');
+ }
+ sliceShardsStr.append(shardUrl);
+ }
+ rb.shards[i] = sliceShardsStr.toString();
}
}
String shards_rows = params.get(ShardParams.SHARDS_ROWS);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
index d1e1ed5..e1b743a 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java
@@ -31,13 +31,13 @@ import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.URLUtil;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.update.UpdateShardHandlerConfig;
+import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.util.DefaultSolrThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
-import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
@@ -84,6 +84,8 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org.
private final Random r = new Random();
+ private final ReplicaListTransformer shufflingReplicaListTransformer = new ShufflingReplicaListTransformer(r);
+
// URL scheme to be used in distributed search.
static final String INIT_URL_SCHEME = "urlScheme";
@@ -227,12 +229,12 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org.
}
/**
- * Creates a randomized list of urls for the given shard.
+ * Creates a list of urls for the given shard.
*
* @param shard the urls for the shard, separated by '|'
* @return A list of valid urls (including protocol) that are replicas for the shard
*/
- public List<String> makeURLList(String shard) {
+ public List<String> buildURLList(String shard) {
List<String> urls = StrUtils.splitSmart(shard, "|", true);
// convert shard to URL
@@ -240,17 +242,14 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org.
urls.set(i, buildUrl(urls.get(i)));
}
- //
- // Shuffle the list instead of use round-robin by default.
- // This prevents accidental synchronization where multiple shards could get in sync
- // and query the same replica at the same time.
- //
- if (urls.size() > 1)
- Collections.shuffle(urls, r);
-
return urls;
}
+ ReplicaListTransformer getReplicaListTransformer(final SolrQueryRequest req)
+ {
+ return shufflingReplicaListTransformer;
+ }
+
/**
* Creates a new completion service for use by a single set of distributed requests.
*/
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/core/src/java/org/apache/solr/handler/component/ReplicaListTransformer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ReplicaListTransformer.java b/solr/core/src/java/org/apache/solr/handler/component/ReplicaListTransformer.java
new file mode 100644
index 0000000..bf30fa6
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/component/ReplicaListTransformer.java
@@ -0,0 +1,35 @@
+/*
+ * 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.handler.component;
+
+import java.util.List;
+
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.params.ShardParams;
+
+interface ReplicaListTransformer {
+
+ /**
+ * Transforms the passed in list of choices. Transformations can include (but are not limited to)
+ * reordering of elements (e.g. via shuffling) and removal of elements (i.e. filtering).
+ *
+ * @param choices - a list of choices to transform, typically the choices are {@link Replica} objects but choices
+ * can also be {@link String} objects such as URLs passed in via the {@link ShardParams#SHARDS} parameter.
+ */
+ public void transform(List<?> choices);
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/core/src/java/org/apache/solr/handler/component/ShufflingReplicaListTransformer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ShufflingReplicaListTransformer.java b/solr/core/src/java/org/apache/solr/handler/component/ShufflingReplicaListTransformer.java
new file mode 100644
index 0000000..428e348
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/component/ShufflingReplicaListTransformer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.handler.component;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+class ShufflingReplicaListTransformer implements ReplicaListTransformer {
+
+ private final Random r;
+
+ public ShufflingReplicaListTransformer(Random r)
+ {
+ this.r = r;
+ }
+
+ public void transform(List<?> choices)
+ {
+ if (choices.size() > 1) {
+ Collections.shuffle(choices, r);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/core/src/test/org/apache/solr/handler/component/ReplicaListTransformerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/component/ReplicaListTransformerTest.java b/solr/core/src/test/org/apache/solr/handler/component/ReplicaListTransformerTest.java
new file mode 100644
index 0000000..96d2319
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/component/ReplicaListTransformerTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.handler.component;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.junit.Test;
+
+public class ReplicaListTransformerTest extends LuceneTestCase {
+
+ // A transformer that keeps only matching choices
+ private static class ToyMatchingReplicaListTransformer implements ReplicaListTransformer {
+
+ private final String regex;
+
+ public ToyMatchingReplicaListTransformer(String regex)
+ {
+ this.regex = regex;
+ }
+
+ public void transform(List<?> choices)
+ {
+ Iterator<?> it = choices.iterator();
+ while (it.hasNext()) {
+ Object choice = it.next();
+ final String url;
+ if (choice instanceof String) {
+ url = (String)choice;
+ }
+ else if (choice instanceof Replica) {
+ url = ((Replica)choice).getCoreUrl();
+ } else {
+ url = null;
+ }
+ if (url == null || !url.matches(regex)) {
+ it.remove();
+ }
+ }
+ }
+
+ }
+
+ // A transformer that makes no transformation
+ private static class ToyNoOpReplicaListTransformer implements ReplicaListTransformer {
+
+ public ToyNoOpReplicaListTransformer()
+ {
+ }
+
+ public void transform(List<?> choices)
+ {
+ // no-op
+ }
+
+ }
+
+ @Test
+ public void testTransform() throws Exception {
+
+ final String regex = ".*" + random().nextInt(10) + ".*";
+
+ final ReplicaListTransformer transformer;
+ if (random().nextBoolean()) {
+
+ transformer = new ToyMatchingReplicaListTransformer(regex);
+
+ } else {
+
+ transformer = new HttpShardHandlerFactory() {
+
+ @Override
+ ReplicaListTransformer getReplicaListTransformer(final SolrQueryRequest req)
+ {
+ final SolrParams params = req.getParams();
+
+ if (params.getBool("toyNoTransform", false)) {
+ return new ToyNoOpReplicaListTransformer();
+ }
+
+ final String regex = params.get("toyRegEx");
+ if (regex != null) {
+ return new ToyMatchingReplicaListTransformer(regex);
+ }
+
+ return super.getReplicaListTransformer(req);
+ }
+
+ }.getReplicaListTransformer(
+ new LocalSolrQueryRequest(null,
+ new ModifiableSolrParams().add("toyRegEx", regex)));
+ }
+
+ final List<Replica> inputs = new ArrayList<>();
+ final List<Replica> expectedTransformed = new ArrayList<>();
+
+ final List<String> urls = createRandomUrls();
+ for (int ii=0; ii<urls.size(); ++ii) {
+
+ final String name = "replica"+(ii+1);
+ final String url = urls.get(ii);
+ final Map<String,Object> propMap = new HashMap<String,Object>();
+ propMap.put("base_url", url);
+ // a skeleton replica, good enough for this test's purposes
+ final Replica replica = new Replica(name, propMap);
+
+ inputs.add(replica);
+ if (url.matches(regex)) {
+ expectedTransformed.add(replica);
+ }
+ }
+
+ final List<Replica> actualTransformed = new ArrayList<>(inputs);
+ transformer.transform(actualTransformed);
+
+ assertEquals(expectedTransformed.size(), actualTransformed.size());
+ for (int ii=0; ii<expectedTransformed.size(); ++ii) {
+ assertEquals("mismatch for ii="+ii, expectedTransformed.get(ii), actualTransformed.get(ii));
+ }
+ }
+
+ private final List<String> createRandomUrls() throws Exception {
+ final List<String> urls = new ArrayList<>();
+ maybeAddUrl(urls, "a"+random().nextDouble());
+ maybeAddUrl(urls, "bb"+random().nextFloat());
+ maybeAddUrl(urls, "ccc"+random().nextGaussian());
+ maybeAddUrl(urls, "dddd"+random().nextInt());
+ maybeAddUrl(urls, "eeeee"+random().nextLong());
+ Collections.shuffle(urls, random());
+ return urls;
+ }
+
+ private final void maybeAddUrl(final List<String> urls, final String url) {
+ if (random().nextBoolean()) {
+ urls.add(url);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c25adb1/solr/core/src/test/org/apache/solr/handler/component/ShufflingReplicaListTransformerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/component/ShufflingReplicaListTransformerTest.java b/solr/core/src/test/org/apache/solr/handler/component/ShufflingReplicaListTransformerTest.java
new file mode 100644
index 0000000..26bb008
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/component/ShufflingReplicaListTransformerTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.handler.component;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.common.cloud.Replica;
+import org.junit.Test;
+
+public class ShufflingReplicaListTransformerTest extends LuceneTestCase {
+
+ private final ShufflingReplicaListTransformer transformer = new ShufflingReplicaListTransformer(random());
+
+ @Test
+ public void testTransformReplicas() throws Exception {
+ final List<Replica> replicas = new ArrayList<>();
+ for (final String url : createRandomUrls()) {
+ replicas.add(new Replica(url, new HashMap<String,Object>()));
+ }
+ implTestTransform(replicas);
+ }
+
+ @Test
+ public void testTransformUrls() throws Exception {
+ final List<String> urls = createRandomUrls();
+ implTestTransform(urls);
+ }
+
+ private <TYPE> void implTestTransform(List<TYPE> inputs) throws Exception {
+ final List<TYPE> transformedInputs = new ArrayList<>(inputs);
+ transformer.transform(transformedInputs);
+
+ final Set<TYPE> inputSet = new HashSet<>(inputs);
+ final Set<TYPE> transformedSet = new HashSet<>(transformedInputs);
+
+ assertTrue(inputSet.equals(transformedSet));
+ }
+
+ private final List<String> createRandomUrls() throws Exception {
+ final List<String> urls = new ArrayList<>();
+ maybeAddUrl(urls, "a"+random().nextDouble());
+ maybeAddUrl(urls, "bb"+random().nextFloat());
+ maybeAddUrl(urls, "ccc"+random().nextGaussian());
+ maybeAddUrl(urls, "dddd"+random().nextInt());
+ maybeAddUrl(urls, "eeeee"+random().nextLong());
+ Collections.shuffle(urls, random());
+ return urls;
+ }
+
+ private final void maybeAddUrl(final List<String> urls, final String url) {
+ if (random().nextBoolean()) {
+ urls.add(url);
+ }
+ }
+
+}