From 2bb6e2cacabdcea6c7534595dfc23cd17973a68d Mon Sep 17 00:00:00 2001 From: Christine Poerschke Date: Thu, 25 May 2017 12:30:30 +0100 Subject: [PATCH] SOLR-10479: Adds support for HttpShardHandlerFactory.loadBalancerRequests(MinimumAbsolute|MaximumFraction) configuration. (Ramsey Haddad, Daniel Collins, Christine Poerschke) --- solr/CHANGES.txt | 3 + .../component/HttpShardHandlerFactory.java | 28 ++++- ...solr-shardhandler-loadBalancerRequests.xml | 23 ++++ .../TestHttpShardHandlerFactory.java | 102 ++++++++++++++++++ .../client/solrj/impl/LBHttpSolrClient.java | 50 ++++++++- 5 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 solr/core/src/test-files/solr/solr-shardhandler-loadBalancerRequests.xml create mode 100644 solr/core/src/test/org/apache/solr/handler/component/TestHttpShardHandlerFactory.java diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 436b80e7066..0ddf6c077ca 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -227,6 +227,9 @@ New Features * SOLR-10721: Provide a way to know when Core Discovery is finished and when all async cores are done loading (Erick Erickson) +* SOLR-10479: Adds support for HttpShardHandlerFactory.loadBalancerRequests(MinimumAbsolute|MaximumFraction) + configuration. (Ramsey Haddad, Daniel Collins, Christine Poerschke) + Bug Fixes ---------------------- * SOLR-10723 JSON Facet API: resize() implemented incorrectly for CountSlotAcc, HllAgg.NumericAcc 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 e3787cdf1ca..73d97078a09 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 @@ -97,6 +97,8 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org. int maximumPoolSize = Integer.MAX_VALUE; int keepAliveTime = 5; int queueSize = -1; + int permittedLoadBalancerRequestsMinimumAbsolute = 0; + float permittedLoadBalancerRequestsMaximumFraction = 1.0f; boolean accessPolicy = false; private String scheme = null; @@ -122,6 +124,12 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org. // If the threadpool uses a backing queue, what is its maximum size (-1) to use direct handoff static final String INIT_SIZE_OF_QUEUE = "sizeOfQueue"; + // The minimum number of replicas that may be used + static final String LOAD_BALANCER_REQUESTS_MIN_ABSOLUTE = "loadBalancerRequestsMinimumAbsolute"; + + // The maximum proportion of replicas to be used + static final String LOAD_BALANCER_REQUESTS_MAX_FRACTION = "loadBalancerRequestsMaximumFraction"; + // Configure if the threadpool favours fairness over throughput static final String INIT_FAIRNESS_POLICY = "fairnessPolicy"; @@ -164,6 +172,16 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org. this.maximumPoolSize = getParameter(args, INIT_MAX_POOL_SIZE, maximumPoolSize,sb); this.keepAliveTime = getParameter(args, MAX_THREAD_IDLE_TIME, keepAliveTime,sb); this.queueSize = getParameter(args, INIT_SIZE_OF_QUEUE, queueSize,sb); + this.permittedLoadBalancerRequestsMinimumAbsolute = getParameter( + args, + LOAD_BALANCER_REQUESTS_MIN_ABSOLUTE, + permittedLoadBalancerRequestsMinimumAbsolute, + sb); + this.permittedLoadBalancerRequestsMaximumFraction = getParameter( + args, + LOAD_BALANCER_REQUESTS_MAX_FRACTION, + permittedLoadBalancerRequestsMaximumFraction, + sb); this.accessPolicy = getParameter(args, INIT_FAIRNESS_POLICY, accessPolicy,sb); log.debug("created with {}",sb); @@ -252,7 +270,15 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory implements org. */ public LBHttpSolrClient.Rsp makeLoadBalancedRequest(final QueryRequest req, List urls) throws SolrServerException, IOException { - return loadbalancer.request(new LBHttpSolrClient.Req(req, urls)); + return loadbalancer.request(newLBHttpSolrClientReq(req, urls)); + } + + protected LBHttpSolrClient.Req newLBHttpSolrClientReq(final QueryRequest req, List urls) { + int numServersToTry = (int)Math.floor(urls.size() * this.permittedLoadBalancerRequestsMaximumFraction); + if (numServersToTry < this.permittedLoadBalancerRequestsMinimumAbsolute) { + numServersToTry = this.permittedLoadBalancerRequestsMinimumAbsolute; + } + return new LBHttpSolrClient.Req(req, urls, numServersToTry); } /** diff --git a/solr/core/src/test-files/solr/solr-shardhandler-loadBalancerRequests.xml b/solr/core/src/test-files/solr/solr-shardhandler-loadBalancerRequests.xml new file mode 100644 index 00000000000..92339d9befb --- /dev/null +++ b/solr/core/src/test-files/solr/solr-shardhandler-loadBalancerRequests.xml @@ -0,0 +1,23 @@ + + + + + ${solr.tests.loadBalancerRequestsMinimumAbsolute:0} + ${solr.tests.loadBalancerRequestsMaximumFraction:1.0} + + diff --git a/solr/core/src/test/org/apache/solr/handler/component/TestHttpShardHandlerFactory.java b/solr/core/src/test/org/apache/solr/handler/component/TestHttpShardHandlerFactory.java new file mode 100644 index 00000000000..3ffa015a26e --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/component/TestHttpShardHandlerFactory.java @@ -0,0 +1,102 @@ +/* + * 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.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.client.solrj.impl.LBHttpSolrClient; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.component.HttpShardHandlerFactory; +import org.apache.solr.handler.component.ShardHandlerFactory; + +import org.junit.BeforeClass; +import org.junit.AfterClass; + +/** + * Tests specifying a custom ShardHandlerFactory + */ +public class TestHttpShardHandlerFactory extends SolrTestCaseJ4 { + + private static final String LOAD_BALANCER_REQUESTS_MIN_ABSOLUTE = "solr.tests.loadBalancerRequestsMinimumAbsolute"; + private static final String LOAD_BALANCER_REQUESTS_MAX_FRACTION = "solr.tests.loadBalancerRequestsMaximumFraction"; + + private static int expectedLoadBalancerRequestsMinimumAbsolute = 0; + private static float expectedLoadBalancerRequestsMaximumFraction = 1.0f; + + @BeforeClass + public static void beforeTests() throws Exception { + expectedLoadBalancerRequestsMinimumAbsolute = random().nextInt(3); // 0 .. 2 + expectedLoadBalancerRequestsMaximumFraction = (1+random().nextInt(10))/10f; // 0.1 .. 1.0 + System.setProperty(LOAD_BALANCER_REQUESTS_MIN_ABSOLUTE, Integer.toString(expectedLoadBalancerRequestsMinimumAbsolute)); + System.setProperty(LOAD_BALANCER_REQUESTS_MAX_FRACTION, Float.toString(expectedLoadBalancerRequestsMaximumFraction)); + } + + @AfterClass + public static void afterTests() { + System.clearProperty(LOAD_BALANCER_REQUESTS_MIN_ABSOLUTE); + System.clearProperty(LOAD_BALANCER_REQUESTS_MAX_FRACTION); + } + + public void testLoadBalancerRequestsMinMax() throws Exception { + final Path home = Paths.get(TEST_HOME()); + CoreContainer cc = null; + ShardHandlerFactory factory = null; + try { + cc = CoreContainer.createAndLoad(home, home.resolve("solr-shardhandler-loadBalancerRequests.xml")); + factory = cc.getShardHandlerFactory(); + + // test that factory is HttpShardHandlerFactory with expected url reserve fraction + assertTrue(factory instanceof HttpShardHandlerFactory); + final HttpShardHandlerFactory httpShardHandlerFactory = ((HttpShardHandlerFactory)factory); + assertEquals(expectedLoadBalancerRequestsMinimumAbsolute, httpShardHandlerFactory.permittedLoadBalancerRequestsMinimumAbsolute, 0.0); + assertEquals(expectedLoadBalancerRequestsMaximumFraction, httpShardHandlerFactory.permittedLoadBalancerRequestsMaximumFraction, 0.0); + + // create a dummy request and dummy url list + final QueryRequest queryRequest = null; + final List urls = new ArrayList<>(); + for (int ii=0; ii<10; ++ii) { + urls.add(null); + } + + // create LBHttpSolrClient request + final LBHttpSolrClient.Req req = httpShardHandlerFactory.newLBHttpSolrClientReq(queryRequest, urls); + + // actual vs. expected test + final int actualNumServersToTry = req.getNumServersToTry().intValue(); + int expectedNumServersToTry = (int)Math.floor(urls.size() * expectedLoadBalancerRequestsMaximumFraction); + if (expectedNumServersToTry < expectedLoadBalancerRequestsMinimumAbsolute) { + expectedNumServersToTry = expectedLoadBalancerRequestsMinimumAbsolute; + } + assertEquals("wrong numServersToTry for" + + " urls.size="+urls.size() + + " expectedLoadBalancerRequestsMinimumAbsolute="+expectedLoadBalancerRequestsMinimumAbsolute + + " expectedLoadBalancerRequestsMaximumFraction="+expectedLoadBalancerRequestsMaximumFraction, + expectedNumServersToTry, + actualNumServersToTry); + + } finally { + if (factory != null) factory.close(); + if (cc != null) cc.shutdown(); + } + } + +} diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java index ed6ae7b99b2..8dc2fd96d42 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttpSolrClient.java @@ -185,11 +185,17 @@ public class LBHttpSolrClient extends SolrClient { protected SolrRequest request; protected List servers; protected int numDeadServersToTry; + private final Integer numServersToTry; public Req(SolrRequest request, List servers) { + this(request, servers, null); + } + + public Req(SolrRequest request, List servers, Integer numServersToTry) { this.request = request; this.servers = servers; this.numDeadServersToTry = servers.size(); + this.numServersToTry = numServersToTry; } public SolrRequest getRequest() { @@ -209,6 +215,10 @@ public class LBHttpSolrClient extends SolrClient { public void setNumDeadServersToTry(int numDeadServersToTry) { this.numDeadServersToTry = numDeadServersToTry; } + + public Integer getNumServersToTry() { + return numServersToTry; + } } public static class Rsp { @@ -360,6 +370,9 @@ public class LBHttpSolrClient extends SolrClient { boolean isNonRetryable = req.request instanceof IsUpdateRequest || ADMIN_PATHS.contains(req.request.getPath()); List skipped = null; + final Integer numServersToTry = req.getNumServersToTry(); + int numServersTried = 0; + boolean timeAllowedExceeded = false; long timeAllowedNano = getTimeAllowedInNanos(req.getRequest()); long timeOutTime = System.nanoTime() + timeAllowedNano; @@ -387,8 +400,14 @@ public class LBHttpSolrClient extends SolrClient { } try { MDC.put("LBHttpSolrClient.url", serverStr); + + if (numServersToTry != null && numServersTried > numServersToTry.intValue()) { + break; + } + HttpSolrClient client = makeSolrClient(serverStr); + ++numServersTried; ex = doRequest(client, req, rsp, isNonRetryable, false, null); if (ex == null) { return rsp; // SUCCESS @@ -405,8 +424,13 @@ public class LBHttpSolrClient extends SolrClient { break; } + if (numServersToTry != null && numServersTried > numServersToTry.intValue()) { + break; + } + try { MDC.put("LBHttpSolrClient.url", wrapper.client.getBaseURL()); + ++numServersTried; ex = doRequest(wrapper.client, req, rsp, isNonRetryable, true, wrapper.getKey()); if (ex == null) { return rsp; // SUCCESS @@ -422,7 +446,13 @@ public class LBHttpSolrClient extends SolrClient { if (timeAllowedExceeded) { solrServerExceptionMessage = "Time allowed to handle this request exceeded"; } else { - solrServerExceptionMessage = "No live SolrServers available to handle this request"; + if (numServersToTry != null && numServersTried > numServersToTry.intValue()) { + solrServerExceptionMessage = "No live SolrServers available to handle this request:" + + " numServersTried="+numServersTried + + " numServersToTry="+numServersToTry.intValue(); + } else { + solrServerExceptionMessage = "No live SolrServers available to handle this request"; + } } if (ex == null) { throw new SolrServerException(solrServerExceptionMessage); @@ -594,10 +624,16 @@ public class LBHttpSolrClient extends SolrClient { @Override public NamedList request(final SolrRequest request, String collection) throws SolrServerException, IOException { + return request(request, collection, null); + } + + public NamedList request(final SolrRequest request, String collection, + final Integer numServersToTry) throws SolrServerException, IOException { Exception ex = null; ServerWrapper[] serverList = aliveServerList; - int maxTries = serverList.length; + final int maxTries = (numServersToTry == null ? serverList.length : numServersToTry.intValue()); + int numServersTried = 0; Map justFailed = null; boolean timeAllowedExceeded = false; @@ -612,6 +648,7 @@ public class LBHttpSolrClient extends SolrClient { ServerWrapper wrapper = serverList[count % serverList.length]; try { + ++numServersTried; return wrapper.client.request(request, collection); } catch (SolrException e) { // Server is alive but the request was malformed or invalid @@ -638,6 +675,7 @@ public class LBHttpSolrClient extends SolrClient { if (wrapper.standard==false || justFailed!=null && justFailed.containsKey(wrapper.getKey())) continue; try { + ++numServersTried; NamedList rsp = wrapper.client.request(request, collection); // remove from zombie list *before* adding to alive to avoid a race that could lose a server zombieServers.remove(wrapper.getKey()); @@ -663,7 +701,13 @@ public class LBHttpSolrClient extends SolrClient { if (timeAllowedExceeded) { solrServerExceptionMessage = "Time allowed to handle this request exceeded"; } else { - solrServerExceptionMessage = "No live SolrServers available to handle this request"; + if (numServersToTry != null && numServersTried > numServersToTry.intValue()) { + solrServerExceptionMessage = "No live SolrServers available to handle this request:" + + " numServersTried="+numServersTried + + " numServersToTry="+numServersToTry.intValue(); + } else { + solrServerExceptionMessage = "No live SolrServers available to handle this request"; + } } if (ex == null) { throw new SolrServerException(solrServerExceptionMessage);