/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2016, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package org.jdiameter.client.impl.router;
import org.jdiameter.api.Configuration;
import org.jdiameter.api.MetaData;
import org.jdiameter.api.PeerState;
import org.jdiameter.client.api.IContainer;
import org.jdiameter.client.api.controller.IPeer;
import org.jdiameter.client.api.controller.IRealmTable;
import org.jdiameter.common.api.concurrent.IConcurrentFactory;
import java.util.List;
/**
* Weighted round-robin router implementation
*
* @see <a href="http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling">http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling</a>
* @author <a href="mailto:n.sowen@2scale.net">Nils Sowen</a>
*/
public class WeightedRoundRobinRouter extends RouterImpl {
private int lastSelectedPeer = -1;
private int currentWeight = 0;
protected WeightedRoundRobinRouter(IRealmTable table, Configuration config) {
super(null, null, table, config, null);
}
public WeightedRoundRobinRouter(IContainer container, IConcurrentFactory concurrentFactory,
IRealmTable realmTable, Configuration config, MetaData aMetaData) {
super(container, concurrentFactory, realmTable, config, aMetaData);
}
/**
* Select peer by weighted round-robin scheduling
* As documented in http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling
*
* <p>
* The weighted round-robin scheduling is designed to better handle servers
* with different processing capacities. Each server can be assigned a weight,
* an integer value that indicates the processing capacity. Servers with higher
* weights receive new connections first than those with less weights, and servers
* with higher weights get more connections than those with less weights and servers
* with equal weights get equal connections. The pseudo code of weighted round-robin
* scheduling is as follows:
* <p>
* Supposing that there is a server set S = {S0, S1, …, Sn-1};
* W(Si) indicates the weight of Si;
* i indicates the server selected last time, and i is initialized with -1;
* cw is the current weight in scheduling, and cw is initialized with zero;
* max(S) is the maximum weight of all the servers in S;
* gcd(S) is the greatest common divisor of all server weights in S;
* <p>
* <pre>
* {@code
* while (true) {
* i = (i + 1) mod n;
* if (i == 0) {
* cw = cw - gcd(S);
* if (cw <= 0) {
* cw = max(S);
* if (cw == 0)
* return NULL;
* }
* }
* if (W(Si) >= cw)
* return Si;
* }
* }
* </pre>
* <p>
* For example, the real servers, A, B and C, have the weights, 4, 3, 2 respectively,
* a scheduling sequence will be AABABCABC in a scheduling period (mod sum(Wi)).
* <p>
* In an optimized implementation of the weighted round-robin scheduling, a scheduling sequence
* will be generated according to the server weights after the rules of IPVS are modified.
* The network connections are directed to the different real servers based on the scheduling
* sequence in a round-robin manner.
* <p>
* The weighted round-robin scheduling is better than the round-robin scheduling, when the
* processing capacity of real servers are different. However, it may lead to dynamic load
* imbalance among the real servers if the load of the requests vary highly. In short, there
* is the possibility that a majority of requests requiring large responses may be directed
* to the same real server.
* <p>
* Actually, the round-robin scheduling is a special instance of the weighted round-robin
* scheduling, in which all the weights are equal.
* <p>
* This method is internally synchronized due to concurrent modifications to lastSelectedPeer and currentWeight.
* Please consider this when relying on heavy throughput.
*
* Please note: if the list of availablePeers changes between calls (e.g. if a peer becomes active or inactive),
* the balancing algorithm is disturbed and might be distributed uneven.
* This is likely to happen if peers are flapping.
*
* @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state
* @return the selected peer according to algorithm
* @see <a href="http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling">http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling</a>
*/
@Override
public IPeer selectPeer(List<IPeer> availablePeers) {
int peerSize = availablePeers != null ? availablePeers.size() : 0;
// Return none if empty, or first if only one member found
if (peerSize <= 0) {
return null;
}
if (peerSize == 1) {
return availablePeers.iterator().next();
}
// Find maximum weight and greatest common divisor of weight across all peers
int maxWeight = 0;
Integer gcd = null;
for (IPeer peer : availablePeers) {
maxWeight = Math.max(maxWeight, peer.getRating());
gcd = (gcd == null) ? peer.getRating() : gcd(gcd, peer.getRating());
}
// Find best matching candidate. Synchronized here due to consistent changes on member variables
synchronized (this) {
for ( ;; ) {
lastSelectedPeer = (lastSelectedPeer + 1) % peerSize;
if (lastSelectedPeer == 0) {
currentWeight = currentWeight - gcd;
if (currentWeight <= 0) {
currentWeight = maxWeight;
}
}
if (peerSize <= lastSelectedPeer) {
lastSelectedPeer = -1; // safety first, restart if peer size has accidentally changed.
continue;
}
IPeer candidate = availablePeers.get(lastSelectedPeer);
if (candidate.getRating() >= currentWeight) {
return availablePeers.get(lastSelectedPeer);
}
}
}
}
/**
* Return greatest common divisor for two integers
* https://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid.27s_algorithm
*
* @param a
* @param b
* @return greatest common divisor
*/
protected int gcd(int a, int b) {
return (b == 0) ? a : gcd(b, a % b);
}
}