/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.rdf.sail.webapp.lbs.policy; import java.util.concurrent.atomic.AtomicLong; import javax.servlet.http.HttpServletRequest; import com.bigdata.rdf.sail.webapp.lbs.AbstractLBSPolicy; import com.bigdata.rdf.sail.webapp.lbs.ServiceScore; /** * Policy implements a round-robin over the services that are joined with the * met quorum. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ public class RoundRobinLBSPolicy extends AbstractLBSPolicy { /** * */ private static final long serialVersionUID = 1L; @Override protected void toString(final StringBuilder sb) { super.toString(sb); sb.append(",nextService=" + nextService.get()); } /** * {@inheritDoc} * <p> * This imposes a round-robin policy over the discovered services. If the * service is discovered and appears to be joined with the met quorum, then * the request can be proxied to that service. */ @Override public String getReaderURI(final HttpServletRequest request) { final ServiceScore[] serviceScores = this.serviceTableRef.get(); if (serviceScores == null) { // Nothing discovered. Can't proxy. return null; } /* * Choose a service. * * Note: This is a round robin over the services. Any service that is * joined with the met quorum can be selected as a target for the read * request. * * Note: The round-robin is atomic with respect to each request. The * request obtains a starting point in the serviceScores[] and then * finds the next service in that array using a round-robin. The * [nextService] starting point is not updated until the round-robin is * complete - this is necessary in order to avoid skipping over services * that are being checked by a concurrent request. * * The [nextService] is updated only after the round-robin decision has * been made. As soon as it has been updated, a new round-robin decision * will be made with respect to the new value for [nextService] but any * in-flight decisions will be made against the value of [nextService] * that they observed on entry. */ // The starting offset for the round-robin. final long startIndex = nextService.longValue(); // The selected service. ServiceScore serviceScore = null; for (int i = 0; i < serviceScores.length; i++) { /* * Find the next host index. * * Note: We need to ensure that the hostIndex stays in the legal * range, even with concurrent requests and when wrapping around * MAX_VALUE. */ final int hostIndex = (int) Math .abs(((i + startIndex) % serviceScores.length)); serviceScore = serviceScores[hostIndex]; if (serviceScore == null) continue; if (serviceScore.getHostname() == null) { // Can't use if no hostname. continue; } if (serviceScore.getRequestURI() == null) { // Can't use if no requestURL. continue; } } // Bump the nextService counter. nextService.incrementAndGet(); if (serviceScore == null) { // No service. Can't proxy. return null; } // track #of requests to each service. serviceScore.nrequests.increment(); return serviceScore.getRequestURI(); } /** * Note: This could be a hot spot. We can have concurrent requests and we * need to increment this counter for each such request. */ private final AtomicLong nextService = new AtomicLong(0L); }