Package org.apache.solr.client.solrj.impl

Source Code of org.apache.solr.client.solrj.impl.LBHttpSolrServer$ServerWrapper

/**
* 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
}
TOP

Related Classes of org.apache.solr.client.solrj.impl.LBHttpSolrServer$ServerWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.