blob: 0f39fc087915088f85dc4331fef122322ba2df4f [file] [log] [blame]
/*
* 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.client.solrj.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.http.client.HttpClient;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.params.ModifiableSolrParams;
/**
* LBHttpSolrClient or "LoadBalanced HttpSolrClient" is a load balancing wrapper around
* {@link HttpSolrClient}. This is useful when you
* have multiple Solr servers and the requests need to be Load Balanced among them.
*
* Do <b>NOT</b> use this class for indexing in leader/follower scenarios since documents must be sent to the
* correct leader; no inter-node routing is done.
*
* In SolrCloud (leader/replica) scenarios, it is usually better to use
* {@link CloudSolrClient}, but this class may be used
* for updates because the server will forward them to the appropriate leader.
*
* <p>
* It offers automatic failover when a server goes down and it detects when the server comes back up.
* <p>
* Load balancing is done using a simple round-robin on the list of servers.
* <p>
* If a request to a server fails by an IOException due to a connection timeout or read timeout then the host is taken
* off the list of live servers and moved to a 'dead server list' and the request is resent to the next live server.
* This process is continued till it tries all the live servers. If at least one server is alive, the request succeeds,
* and if not it fails.
* <blockquote><pre>
* SolrClient lbHttpSolrClient = new LBHttpSolrClient("http://host1:8080/solr/", "http://host2:8080/solr", "http://host2:8080/solr");
* //or if you wish to pass the HttpClient do as follows
* httpClient httpClient = new HttpClient();
* SolrClient lbHttpSolrClient = new LBHttpSolrClient(httpClient, "http://host1:8080/solr/", "http://host2:8080/solr", "http://host2:8080/solr");
* </pre></blockquote>
* This detects if a dead server comes alive automatically. The check is done in fixed intervals in a dedicated thread.
* This interval can be set using {@link #setAliveCheckInterval} , the default is set to one minute.
* <p>
* <b>When to use this?</b><br> This can be used as a software load balancer when you do not wish to setup an external
* load balancer. Alternatives to this code are to use
* a dedicated hardware load balancer or using Apache httpd with mod_proxy_balancer as a load balancer. See <a
* href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load balancing on Wikipedia</a>
*
* @since solr 1.4
*/
public class LBHttpSolrClient extends LBSolrClient {
private final HttpClient httpClient;
private final boolean clientIsInternal;
private final ConcurrentHashMap<String, HttpSolrClient> urlToClient = new ConcurrentHashMap<>();
private final HttpSolrClient.Builder httpSolrClientBuilder;
private Integer connectionTimeout;
private volatile Integer soTimeout;
/**
* @deprecated use {@link LBSolrClient.Req} instead
*/
@Deprecated
public static class Req extends LBSolrClient.Req {
public Req(@SuppressWarnings({"rawtypes"})SolrRequest request, List<String> servers) {
super(request, servers);
}
public Req(@SuppressWarnings({"rawtypes"})SolrRequest request, List<String> servers, Integer numServersToTry) {
super(request, servers, numServersToTry);
}
}
/**
* @deprecated use {@link LBSolrClient.Rsp} instead
*/
@Deprecated
public static class Rsp extends LBSolrClient.Rsp {
}
/**
* The provided httpClient should use a multi-threaded connection manager
*
* @deprecated use {@link LBHttpSolrClient#LBHttpSolrClient(Builder)} instead, as it is a more extension/subclassing-friendly alternative
*/
@Deprecated
protected LBHttpSolrClient(HttpSolrClient.Builder httpSolrClientBuilder,
HttpClient httpClient, String... solrServerUrl) {
this(new Builder()
.withHttpSolrClientBuilder(httpSolrClientBuilder)
.withHttpClient(httpClient)
.withBaseSolrUrls(solrServerUrl));
}
/**
* The provided httpClient should use a multi-threaded connection manager
*
* @deprecated use {@link LBHttpSolrClient#LBHttpSolrClient(Builder)} instead, as it is a more extension/subclassing-friendly alternative
*/
@Deprecated
protected LBHttpSolrClient(HttpClient httpClient, ResponseParser parser, String... solrServerUrl) {
this(new Builder()
.withBaseSolrUrls(solrServerUrl)
.withResponseParser(parser)
.withHttpClient(httpClient));
}
protected LBHttpSolrClient(Builder builder) {
super(builder.baseSolrUrls);
this.clientIsInternal = builder.httpClient == null;
this.httpSolrClientBuilder = builder.httpSolrClientBuilder;
this.httpClient = builder.httpClient == null ? constructClient(builder.baseSolrUrls.toArray(new String[builder.baseSolrUrls.size()])) : builder.httpClient;
this.connectionTimeout = builder.connectionTimeoutMillis;
this.soTimeout = builder.socketTimeoutMillis;
this.parser = builder.responseParser;
for (String baseUrl: builder.baseSolrUrls) {
urlToClient.put(baseUrl, makeSolrClient(baseUrl));
}
}
private HttpClient constructClient(String[] solrServerUrl) {
ModifiableSolrParams params = new ModifiableSolrParams();
if (solrServerUrl != null && solrServerUrl.length > 1) {
// we prefer retrying another server
params.set(HttpClientUtil.PROP_USE_RETRY, false);
} else {
params.set(HttpClientUtil.PROP_USE_RETRY, true);
}
return HttpClientUtil.createClient(params);
}
protected HttpSolrClient makeSolrClient(String server) {
HttpSolrClient client;
if (httpSolrClientBuilder != null) {
synchronized (this) {
httpSolrClientBuilder
.withBaseSolrUrl(server)
.withHttpClient(httpClient);
if (connectionTimeout != null) {
httpSolrClientBuilder.withConnectionTimeout(connectionTimeout);
}
if (soTimeout != null) {
httpSolrClientBuilder.withSocketTimeout(soTimeout);
}
client = httpSolrClientBuilder.build();
}
} else {
final HttpSolrClient.Builder clientBuilder = new HttpSolrClient.Builder(server)
.withHttpClient(httpClient)
.withResponseParser(parser);
if (connectionTimeout != null) {
clientBuilder.withConnectionTimeout(connectionTimeout);
}
if (soTimeout != null) {
clientBuilder.withSocketTimeout(soTimeout);
}
client = clientBuilder.build();
}
if (requestWriter != null) {
client.setRequestWriter(requestWriter);
}
if (queryParams != null) {
client.setQueryParams(queryParams);
}
return client;
}
/**
* @deprecated since 7.0 Use {@link Builder} methods instead.
*/
@Deprecated
public void setConnectionTimeout(int timeout) {
this.connectionTimeout = timeout;
this.urlToClient.values().forEach(client -> client.setConnectionTimeout(timeout));
}
/**
* set soTimeout (read timeout) on the underlying HttpConnectionManager. This is desirable for queries, but probably
* not for indexing.
*
* @deprecated since 7.0 Use {@link Builder} methods instead.
*/
@Deprecated
public void setSoTimeout(int timeout) {
this.soTimeout = timeout;
this.urlToClient.values().forEach(client -> client.setSoTimeout(timeout));
}
/**
* @deprecated use {@link LBSolrClient#request(LBSolrClient.Req)} instead
*/
@Deprecated
public Rsp request(Req req) throws SolrServerException, IOException {
LBSolrClient.Rsp rsp = super.request(req);
// for backward-compatibility support
Rsp result = new Rsp();
result.rsp = rsp.rsp;
result.server = rsp.server;
return result;
}
@Override
protected SolrClient getClient(String baseUrl) {
HttpSolrClient client = urlToClient.get(baseUrl);
if (client == null) {
return makeSolrClient(baseUrl);
} else {
return client;
}
}
@Override
public String removeSolrServer(String server) {
urlToClient.remove(server);
return super.removeSolrServer(server);
}
@Override
public void close() {
super.close();
if(clientIsInternal) {
HttpClientUtil.close(httpClient);
}
}
/**
* Return the HttpClient this instance uses.
*/
public HttpClient getHttpClient() {
return httpClient;
}
/**
* Constructs {@link LBHttpSolrClient} instances from provided configuration.
*/
public static class Builder extends SolrClientBuilder<Builder> {
protected final List<String> baseSolrUrls;
protected HttpSolrClient.Builder httpSolrClientBuilder;
public Builder() {
this.baseSolrUrls = new ArrayList<>();
this.responseParser = new BinaryResponseParser();
}
public HttpSolrClient.Builder getHttpSolrClientBuilder() {
return httpSolrClientBuilder;
}
/**
* Provide a Solr endpoint to be used when configuring {@link LBHttpSolrClient} instances.
*
* Method may be called multiple times. All provided values will be used.
*
* Two different paths can be specified as a part of the URL:
*
* 1) A path pointing directly at a particular core
* <pre>
* SolrClient client = builder.withBaseSolrUrl("http://my-solr-server:8983/solr/core1").build();
* QueryResponse resp = client.query(new SolrQuery("*:*"));
* </pre>
* Note that when a core is provided in the base URL, queries and other requests can be made without mentioning the
* core explicitly. However, the client can only send requests to that core.
*
* 2) The path of the root Solr path ("/solr")
* <pre>
* SolrClient client = builder.withBaseSolrUrl("http://my-solr-server:8983/solr").build();
* QueryResponse resp = client.query("core1", new SolrQuery("*:*"));
* </pre>
* In this case the client is more flexible and can be used to send requests to any cores. This flexibility though
* requires that the core is specified on all requests.
*/
public Builder withBaseSolrUrl(String baseSolrUrl) {
this.baseSolrUrls.add(baseSolrUrl);
return this;
}
/**
* Provide Solr endpoints to be used when configuring {@link LBHttpSolrClient} instances.
*
* Method may be called multiple times. All provided values will be used.
*
* Two different paths can be specified as a part of each URL:
*
* 1) A path pointing directly at a particular core
* <pre>
* SolrClient client = builder.withBaseSolrUrls("http://my-solr-server:8983/solr/core1").build();
* QueryResponse resp = client.query(new SolrQuery("*:*"));
* </pre>
* Note that when a core is provided in the base URL, queries and other requests can be made without mentioning the
* core explicitly. However, the client can only send requests to that core.
*
* 2) The path of the root Solr path ("/solr")
* <pre>
* SolrClient client = builder.withBaseSolrUrls("http://my-solr-server:8983/solr").build();
* QueryResponse resp = client.query("core1", new SolrQuery("*:*"));
* </pre>
* In this case the client is more flexible and can be used to send requests to any cores. This flexibility though
* requires that the core is specified on all requests.
*/
public Builder withBaseSolrUrls(String... solrUrls) {
for (String baseSolrUrl : solrUrls) {
this.baseSolrUrls.add(baseSolrUrl);
}
return this;
}
/**
* Provides a {@link HttpSolrClient.Builder} to be used for building the internally used clients.
*/
public Builder withHttpSolrClientBuilder(HttpSolrClient.Builder builder) {
this.httpSolrClientBuilder = builder;
return this;
}
/**
* Create a {@link HttpSolrClient} based on provided configuration.
*/
public LBHttpSolrClient build() {
return new LBHttpSolrClient(this);
}
@Override
public Builder getThis() {
return this;
}
}
}