org.apache.solr.client.solrj.impl.LBHttpSolrServer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.client.solrj.impl.LBHttpSolrServer.java

Source

/**
 * 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 org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.solr.client.solrj.*;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.SolrException;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * LBHttpSolrServer or "LoadBalanced HttpSolrServer" is just a wrapper to CommonsHttpSolrServer. This is useful when you
 * have multiple SolrServers and the requests need to be Load Balanced among them. This should <b>NOT</b> be used for
 * indexing. Also see the <a href="http://wiki.apache.org/solr/LBHttpSolrServer">wiki</a> page.
 * <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 roundrobin 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 atleast one server is alive, the request succeeds,
 * andif not it fails.
 * <blockquote><pre>
 * SolrServer lbHttpSolrServer = new LBHttpSolrServer("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();
 * SolrServer lbHttpSolrServer = new LBHttpSolrServer(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. The code is relatively new and the API is currently experimental. 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 LBHttpSolrServer extends SolrServer {
    private final CopyOnWriteArrayList<ServerWrapper> aliveServers = new CopyOnWriteArrayList<ServerWrapper>();
    private final CopyOnWriteArrayList<ServerWrapper> zombieServers = new CopyOnWriteArrayList<ServerWrapper>();
    private ScheduledExecutorService aliveCheckExecutor;

    private HttpClient httpClient;
    private final AtomicInteger counter = new AtomicInteger(-1);

    private ReentrantLock checkLock = new ReentrantLock();
    private static final SolrQuery solrQuery = new SolrQuery("*:*");

    static {
        solrQuery.setRows(0);
    }

    private static class ServerWrapper {
        final CommonsHttpSolrServer solrServer;

        // Used only by the thread in aliveCheckExecutor
        long lastUsed, lastChecked;

        int failedPings = 0;

        public ServerWrapper(CommonsHttpSolrServer solrServer) {
            this.solrServer = solrServer;
        }

        @Override
        public String toString() {
            return solrServer.getBaseURL();
        }
    }

    public LBHttpSolrServer(String... solrServerUrls) throws MalformedURLException {
        this(new HttpClient(new MultiThreadedHttpConnectionManager()), solrServerUrls);
    }

    public LBHttpSolrServer(HttpClient httpClient, String... solrServerUrl) throws MalformedURLException {
        this(httpClient, new BinaryResponseParser(), solrServerUrl);
    }

    public LBHttpSolrServer(HttpClient httpClient, ResponseParser parser, String... solrServerUrl)
            throws MalformedURLException {
        this.httpClient = httpClient;
        for (String s : solrServerUrl) {
            aliveServers.add(new ServerWrapper(new CommonsHttpSolrServer(s, httpClient, parser)));
        }
    }

    public void addSolrServer(String server) throws MalformedURLException {
        CommonsHttpSolrServer solrServer = new CommonsHttpSolrServer(server, httpClient);
        checkLock.lock();
        try {
            aliveServers.add(new ServerWrapper(solrServer));
        } finally {
            checkLock.unlock();
        }
    }

    public String removeSolrServer(String server) {
        try {
            server = new URL(server).toExternalForm();
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
        if (server.endsWith("/")) {
            server = server.substring(0, server.length() - 1);
        }
        this.checkLock.lock();
        try {
            for (ServerWrapper serverWrapper : aliveServers) {
                if (serverWrapper.solrServer.getBaseURL().equals(server)) {
                    aliveServers.remove(serverWrapper);
                    return serverWrapper.solrServer.getBaseURL();
                }
            }
            if (zombieServers.isEmpty())
                return null;

            for (ServerWrapper serverWrapper : zombieServers) {
                if (serverWrapper.solrServer.getBaseURL().equals(server)) {
                    zombieServers.remove(serverWrapper);
                    return serverWrapper.solrServer.getBaseURL();
                }
            }
        } finally {
            checkLock.unlock();
        }
        return null;
    }

    public void setConnectionTimeout(int timeout) {
        httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(timeout);
    }

    /**
     * set connectionManagerTimeout on the HttpClient.*
     */
    public void setConnectionManagerTimeout(int timeout) {
        httpClient.getParams().setConnectionManagerTimeout(timeout);
    }

    /**
     * set soTimeout (read timeout) on the underlying HttpConnectionManager. This is desirable for queries, but probably
     * not for indexing.
     */
    public void setSoTimeout(int timeout) {
        httpClient.getParams().setSoTimeout(timeout);
    }

    /**
     * Tries to query a live server. If no live servers are found it throws a SolrServerException. If the request failed
     * due to IOException then the live server is moved to dead pool and the request is retried on another live server if
     * available. If all live servers are exhausted then a SolrServerException is thrown.
     *
     * @param request the SolrRequest.
     *
     * @return response
     *
     * @throws SolrServerException
     * @throws IOException
     */
    @Override
    public NamedList<Object> request(final SolrRequest request) throws SolrServerException, IOException {
        int count = counter.incrementAndGet();
        int attempts = 0;
        Exception ex;
        int startSize = aliveServers.size();
        while (true) {
            int size = aliveServers.size();
            if (size < 1)
                throw new SolrServerException("No live SolrServers available to handle this request");
            ServerWrapper solrServer;
            try {
                solrServer = aliveServers.get(count % size);
            } catch (IndexOutOfBoundsException e) {
                //this list changes dynamically. so it is expected to get IndexOutOfBoundsException
                continue;
            }
            try {
                return solrServer.solrServer.request(request);
            } catch (SolrException e) {
                // Server is alive but the request was malformed or invalid
                throw e;
            } catch (SolrServerException e) {
                if (e.getRootCause() instanceof IOException) {
                    ex = e;
                    moveAliveToDead(solrServer);
                } else {
                    throw e;
                }
            } catch (Exception e) {
                throw new SolrServerException(e);
            }
            attempts++;
            if (attempts >= startSize)
                throw new SolrServerException("No live SolrServers available to handle this request", ex);
        }
    }

    /**
     * Takes up one dead server and check for aliveness. The check is done in a roundrobin. Each server is checked for
     * aliveness once in 'x' millis where x is decided by the setAliveCheckinterval() or it is defaulted to 1 minute
     *
     * @param zombieServer a server in the dead pool
     */
    private void checkAZombieServer(ServerWrapper zombieServer) {
        long currTime = System.currentTimeMillis();
        checkLock.lock();
        try {
            zombieServer.lastChecked = currTime;
            QueryResponse resp = zombieServer.solrServer.query(solrQuery);
            if (resp.getStatus() == 0) {
                //server has come back up
                zombieServer.lastUsed = currTime;
                zombieServers.remove(zombieServer);
                aliveServers.add(zombieServer);
                zombieServer.failedPings = 0;
            }
        } catch (Exception e) {
            zombieServer.failedPings++;
            //Expected . The server is still down
        } finally {
            checkLock.unlock();
        }
    }

    private void moveAliveToDead(ServerWrapper solrServer) {
        checkLock.lock();
        try {
            boolean result = aliveServers.remove(solrServer);
            if (result) {
                if (zombieServers.addIfAbsent(solrServer)) {
                    startAliveCheckExecutor();
                }
            }
        } finally {
            checkLock.unlock();
        }
    }

    private int interval = CHECK_INTERVAL;

    /**
     * LBHttpSolrServer keeps pinging the dead servers at fixed interval to find if it is alive. Use this to set that
     * interval
     *
     * @param interval time in milliseconds
     */
    public void setAliveCheckInterval(int interval) {
        if (interval <= 0) {
            throw new IllegalArgumentException(
                    "Alive check interval must be " + "positive, specified value = " + interval);
        }
        this.interval = interval;
    }

    private void startAliveCheckExecutor() {
        if (aliveCheckExecutor == null) {
            synchronized (this) {
                if (aliveCheckExecutor == null) {
                    aliveCheckExecutor = Executors.newSingleThreadScheduledExecutor();
                    aliveCheckExecutor.scheduleAtFixedRate(
                            getAliveCheckRunner(new WeakReference<LBHttpSolrServer>(this)), this.interval,
                            this.interval, TimeUnit.MILLISECONDS);
                }
            }
        }
    }

    private static Runnable getAliveCheckRunner(final WeakReference<LBHttpSolrServer> lbHttpSolrServer) {
        return new Runnable() {
            public void run() {
                LBHttpSolrServer solrServer = lbHttpSolrServer.get();
                if (solrServer != null && solrServer.zombieServers != null) {
                    for (ServerWrapper zombieServer : solrServer.zombieServers) {
                        solrServer.checkAZombieServer(zombieServer);
                    }
                }
            }
        };
    }

    public HttpClient getHttpClient() {
        return httpClient;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (this.aliveCheckExecutor != null)
                this.aliveCheckExecutor.shutdownNow();
        } finally {
            super.finalize();
        }
    }

    private static final int CHECK_INTERVAL = 60 * 1000; //1 minute between checks
}