在服务器集群中,需要用负载均衡器在集群中选择合适的服务器进行响应请求,其中负载均衡器通过负载均衡算法来选择服务器的。因此,负载均衡算法的选择会影响服务整体的性能问题。一般,常见的负载均衡算法有轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接数算法等。
1.轮询法
轮询法即逐个请求,对于每一个请求,则在地址列表中依次选择一个地址,因此在请求数目上的分配相对比较均衡。
通过以下示例来进行解释:
轮询方法类:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class RoundRobin {
private static final ArrayList<String> arr = new ArrayList<>();
private static Integer pos = 0;
static {
arr.add("192.168.1.100");
arr.add("192.168.1.101");
arr.add("192.168.1.102");
arr.add("192.168.1.103");
arr.add("192.168.1.104");
arr.add("192.168.1.105");
arr.add("192.168.1.106");
arr.add("192.168.1.107");
arr.add("192.168.1.108");
arr.add("192.168.1.109");
arr.add("192.168.1.110");
}
//轮询法获取ip
public static String testRoundRobin(int requestId) {
String server = null;
synchronized (pos) {
if(pos>=arr.size())
pos = 0;
server = arr.get(pos);
pos++;
}
return requestId+"响应的服务器ip是:"+server;
}
}
轮询工作线程类:
public class GetServerThread extends Thread {
private String server = null;
public int requestId ;
public GetServerThread(int requestId) {
// TODO Auto-generated constructor stub
this.requestId = requestId;
}
@Override
public void run() {
synchronized (this) {
server = RoundRobin.testRoundRobin(requestId);
System.out.println(server);
}
}
}
测试类:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
for(int i=0;i<30;i++) {
Thread thread = new GetServerThread(i+1);
thread.start();
thread.join();
}
}
}
运行程序测试结果如下:
1响应的服务器ip是:192.168.1.100
2响应的服务器ip是:192.168.1.101
3响应的服务器ip是:192.168.1.102
4响应的服务器ip是:192.168.1.103
5响应的服务器ip是:192.168.1.104
6响应的服务器ip是:192.168.1.105
7响应的服务器ip是:192.168.1.106
8响应的服务器ip是:192.168.1.107
9响应的服务器ip是:192.168.1.108
10响应的服务器ip是:192.168.1.109
11响应的服务器ip是:192.168.1.110
12响应的服务器ip是:192.168.1.100
13响应的服务器ip是:192.168.1.101
14响应的服务器ip是:192.168.1.102
15响应的服务器ip是:192.168.1.103
16响应的服务器ip是:192.168.1.104
17响应的服务器ip是:192.168.1.105
18响应的服务器ip是:192.168.1.106
19响应的服务器ip是:192.168.1.107
20响应的服务器ip是:192.168.1.108
21响应的服务器ip是:192.168.1.109
22响应的服务器ip是:192.168.1.110
23响应的服务器ip是:192.168.1.100
24响应的服务器ip是:192.168.1.101
25响应的服务器ip是:192.168.1.102
26响应的服务器ip是:192.168.1.103
27响应的服务器ip是:192.168.1.104
28响应的服务器ip是:192.168.1.105
29响应的服务器ip是:192.168.1.106
30响应的服务器ip是:192.168.1.107
由以上结果可以看出,30个请求依次在列表中进行分配,因此相对来说,每个服务器分配到的请求数比较均衡。
2.随机法
随机法则是随机分配给地址列表中的任一服务器。这个例子则通过随机生成列表长度内的一个整数,然后获取其地址来实现的。
随机方法类:
import java.util.ArrayList;
import java.util.Random;
public class RandomMethod {
private static final ArrayList<String> arr = new ArrayList<>();
private static Integer pos = 0;
static {
arr.add("192.168.1.100");
arr.add("192.168.1.101");
arr.add("192.168.1.102");
arr.add("192.168.1.103");
arr.add("192.168.1.104");
arr.add("192.168.1.105");
arr.add("192.168.1.106");
arr.add("192.168.1.107");
arr.add("192.168.1.108");
arr.add("192.168.1.109");
arr.add("192.168.1.110");
}
public static String testRandom(int requestId) {
String server = null;
synchronized (pos) {
Random random = new Random();
pos=random.nextInt(arr.size());
server = arr.get(pos);
}
return "响应请求"+requestId+"的服务器ip是:"+server;
}
}
线程类:
public class GetRandomThread extends Thread{
private static int requestId;
public GetRandomThread(int requestId) {
// TODO Auto-generated constructor stub
this.requestId = requestId;
}
public void run() {
synchronized (this) {
String server = RandomMethod.testRandom(requestId);
System.out.println(server);
}
}
}
测试类:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
for(int i=0;i<30;i++) {
Thread thread = new GetRandomThread(i+1);
thread.start();
thread.join();
}
}
}
运行结果:
响应请求1的服务器ip是:192.168.1.102
响应请求2的服务器ip是:192.168.1.105
响应请求3的服务器ip是:192.168.1.102
响应请求4的服务器ip是:192.168.1.105
响应请求5的服务器ip是:192.168.1.106
响应请求6的服务器ip是:192.168.1.109
响应请求7的服务器ip是:192.168.1.106
响应请求8的服务器ip是:192.168.1.105
响应请求9的服务器ip是:192.168.1.107
响应请求10的服务器ip是:192.168.1.101
响应请求11的服务器ip是:192.168.1.108
响应请求12的服务器ip是:192.168.1.100
响应请求13的服务器ip是:192.168.1.106
响应请求14的服务器ip是:192.168.1.109
响应请求15的服务器ip是:192.168.1.105
响应请求16的服务器ip是:192.168.1.104
响应请求17的服务器ip是:192.168.1.103
响应请求18的服务器ip是:192.168.1.101
响应请求19的服务器ip是:192.168.1.107
响应请求20的服务器ip是:192.168.1.110
响应请求21的服务器ip是:192.168.1.110
响应请求22的服务器ip是:192.168.1.110
响应请求23的服务器ip是:192.168.1.108
响应请求24的服务器ip是:192.168.1.107
响应请求25的服务器ip是:192.168.1.103
响应请求26的服务器ip是:192.168.1.106
响应请求27的服务器ip是:192.168.1.108
响应请求28的服务器ip是:192.168.1.107
响应请求29的服务器ip是:192.168.1.109
响应请求30的服务器ip是:192.168.1.103
根据运行结果来看,随机法分配并没有一定规律。但是当吞吐量比较大的时候,随机法结果接近于轮询法。
3.源地址哈希法
该方法通过对发送请求的客户端的ip地址进行求hash值,并对服务地址列表长度取余,选择结果对应的服务器。该方法保证了同一客户端ip地址会被映射到相同的后端服务器上,直到后端服务器列表发生了改变。根据这个特性,可以在服务消费者和服务提供者之间建立有状态的session会话。
源地址哈希法类:
import java.util.ArrayList;
public class HashMthod {
private static final ArrayList<String> arr = new ArrayList<>();
private static Integer pos = 0;
static {
arr.add("192.168.1.100");
arr.add("192.168.1.101");
arr.add("192.168.1.102");
arr.add("192.168.1.103");
arr.add("192.168.1.104");
arr.add("192.168.1.105");
arr.add("192.168.1.106");
arr.add("192.168.1.107");
arr.add("192.168.1.108");
arr.add("192.168.1.109");
arr.add("192.168.1.110");
}
public static String testConsumerHash(String consumerIP) {
String server = null;
synchronized (pos) {
pos = Math.abs(consumerIP.hashCode())%arr.size();
server = arr.get(pos);
}
return consumerIP+"的响应的服务器ip是:"+server;
}
}
线程类:
public class GetHashThread extends Thread {
private String consumerIP;
public GetHashThread(String consumerIP) {
this.consumerIP = consumerIP;
}
public void run() {
synchronized (this) {
String server = HashMthod.testConsumerHash(consumerIP);
System.out.println(server);
}
}
}
测试类:
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
Random random = new Random();
for(int i=0;i<30;i++) {
int rand = random.nextInt(255);
Thread thread = new GetHashThread("192.168.111."+rand);
thread.start();
thread.join();
}
}
}
运行结果:
192.168.111.220的响应的服务器ip是:192.168.1.110
192.168.111.236的响应的服务器ip是:192.168.1.103
192.168.111.25的响应的服务器ip是:192.168.1.110
192.168.111.209的响应的服务器ip是:192.168.1.101
192.168.111.31的响应的服务器ip是:192.168.1.104
192.168.111.165的响应的服务器ip是:192.168.1.103
192.168.111.130的响应的服务器ip是:192.168.1.104
192.168.111.154的响应的服务器ip是:192.168.1.104
192.168.111.172的响应的服务器ip是:192.168.1.109
192.168.111.14的响应的服务器ip是:192.168.1.100
192.168.111.30的响应的服务器ip是:192.168.1.103
192.168.111.223的响应的服务器ip是:192.168.1.102
192.168.111.138的响应的服务器ip是:192.168.1.101
192.168.111.30的响应的服务器ip是:192.168.1.103
192.168.111.185的响应的服务器ip是:192.168.1.110
192.168.111.229的响应的服务器ip是:192.168.1.108
192.168.111.42的响应的服务器ip是:192.168.1.103
192.168.111.221的响应的服务器ip是:192.168.1.100
192.168.111.134的响应的服务器ip是:192.168.1.108
192.168.111.17的响应的服务器ip是:192.168.1.103
192.168.111.49的响应的服务器ip是:192.168.1.110
192.168.111.231的响应的服务器ip是:192.168.1.109
192.168.111.30的响应的服务器ip是:192.168.1.103
192.168.111.90的响应的服务器ip是:192.168.1.102
192.168.111.26的响应的服务器ip是:192.168.1.100
192.168.111.131的响应的服务器ip是:192.168.1.105
192.168.111.156的响应的服务器ip是:192.168.1.106
192.168.111.207的响应的服务器ip是:192.168.1.110
192.168.111.190的响应的服务器ip是:192.168.1.103
192.168.111.146的响应的服务器ip是:192.168.1.108
根据以上结果可以看出,出现了多次ip:192.168.111.30,最终响应的服务器都是192.168.1.103.验证了相同客户端ip可以映射到同一后端服务器。
4.加权轮询法
加权轮询法则是给每个服务器都设置了权重,配置低、负载高的服务器权重低,配置高、负载低的服务器权重高。这说明应该让权重高的服务器接收到请求的概率更高。因此可以根据权重在列表中复制相同ip地址,然后根据轮询法来选择相应ip地址,可以满足权重高的被选择的概率相对较高。
加权轮询法类:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class WeightRoundRobin {
private static final ArrayList<String> arr = new ArrayList<>();
private static final HashMap<String, Integer> serverWeight = new HashMap<>();
private static Integer pos = 0;
static {
serverWeight.put("192.168.1.100", 1);
serverWeight.put("192.168.1.101", 1);
serverWeight.put("192.168.1.102", 4);
serverWeight.put("192.168.1.103", 1);
serverWeight.put("192.168.1.104", 1);
serverWeight.put("192.168.1.105", 3);
serverWeight.put("192.168.1.106", 1);
serverWeight.put("192.168.1.107", 2);
serverWeight.put("192.168.1.108", 1);
serverWeight.put("192.168.1.109", 1);
serverWeight.put("192.168.1.110", 1);
//重新创建一个map,避免出现由于服务器上线和下线导致的并发问题
Map<String, Integer> serverMap = new HashMap<>();
serverMap.putAll(serverWeight);
//取得IP地址list
Set<String> keySet = serverMap.keySet();
Iterator<String> it = keySet.iterator();
while(it.hasNext()) {
String server = it.next();
Integer weight = serverWeight.get(server);
for(int i=0;i<weight;i++) {
arr.add(server);
}
}
}
public static String testWeightRoundRobin(int requestId) {
String server = null;
synchronized (pos) {
if(pos>=arr.size())
pos = 0;
server = arr.get(pos);
pos++;
}
return "响应请求"+requestId+"的服务器ip是:"+server;
}
}
线程类:
public class GetWeightRoundRobin extends Thread{
private int requestId;
public GetWeightRoundRobin(int requestId) {
// TODO Auto-generated constructor stub
this.requestId = requestId;
}
public void run() {
synchronized (this) {
String server = WeightRoundRobin.testWeightRoundRobin(requestId);
System.out.println(server);
}
}
}
测试类:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
for(int i=0;i<30;i++) {
Thread thread = new GetWeightRoundRobin(i+1);
thread.start();
thread.join();
}
}
}
运行结果:
响应请求1的服务器ip是:192.168.1.107
响应请求2的服务器ip是:192.168.1.107
响应请求3的服务器ip是:192.168.1.108
响应请求4的服务器ip是:192.168.1.109
响应请求5的服务器ip是:192.168.1.103
响应请求6的服务器ip是:192.168.1.104
响应请求7的服务器ip是:192.168.1.105
响应请求8的服务器ip是:192.168.1.105
响应请求9的服务器ip是:192.168.1.105
响应请求10的服务器ip是:192.168.1.106
响应请求11的服务器ip是:192.168.1.110
响应请求12的服务器ip是:192.168.1.100
响应请求13的服务器ip是:192.168.1.101
响应请求14的服务器ip是:192.168.1.102
响应请求15的服务器ip是:192.168.1.102
响应请求16的服务器ip是:192.168.1.102
响应请求17的服务器ip是:192.168.1.102
响应请求18的服务器ip是:192.168.1.107
响应请求19的服务器ip是:192.168.1.107
响应请求20的服务器ip是:192.168.1.108
响应请求21的服务器ip是:192.168.1.109
响应请求22的服务器ip是:192.168.1.103
响应请求23的服务器ip是:192.168.1.104
响应请求24的服务器ip是:192.168.1.105
响应请求25的服务器ip是:192.168.1.105
响应请求26的服务器ip是:192.168.1.105
响应请求27的服务器ip是:192.168.1.106
响应请求28的服务器ip是:192.168.1.110
响应请求29的服务器ip是:192.168.1.100
响应请求30的服务器ip是:192.168.1.101
从运行结果可以看出,权重较高的服务器出现的频率相对较高。
5.加权随机法
该方法的转换思路与加权轮询法一致,只不过后面一步是用随机法。
加权随机法类:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public class WeightRandom {
private static final ArrayList<String> arr = new ArrayList<>();
private static final HashMap<String, Integer> serverWeight = new HashMap<>();
private static Integer pos = 0;
static {
serverWeight.put("192.168.1.100", 1);
serverWeight.put("192.168.1.101", 1);
serverWeight.put("192.168.1.102", 4);
serverWeight.put("192.168.1.103", 1);
serverWeight.put("192.168.1.104", 1);
serverWeight.put("192.168.1.105", 3);
serverWeight.put("192.168.1.106", 1);
serverWeight.put("192.168.1.107", 2);
serverWeight.put("192.168.1.108", 1);
serverWeight.put("192.168.1.109", 1);
serverWeight.put("192.168.1.110", 1);
//重新创建一个map,避免出现由于服务器上线和下线导致的并发问题
Map<String, Integer> serverMap = new HashMap<>();
serverMap.putAll(serverWeight);
//取得IP地址list
Set<String> keySet = serverMap.keySet();
Iterator<String> it = keySet.iterator();
while(it.hasNext()) {
String server = it.next();
Integer weight = serverWeight.get(server);
for(int i=0;i<weight;i++) {
arr.add(server);
}
}
}
public static String testWeightRandom(int requestId) {
// TODO Auto-generated method stub
String server = null;
synchronized (pos) {
Random random = new Random();
pos = random.nextInt(arr.size());
server = arr.get(pos);
}
return "响应请求"+requestId+"的服务器ip是:"+server;
}
}
线程类:
public class GetWeightRandomThread extends Thread{
private int requestId;
public GetWeightRandomThread(int requestId) {
// TODO Auto-generated constructor stub
this.requestId = requestId;
}
public void run() {
synchronized (this) {
String server = WeightRandom.testWeightRandom(requestId);
System.out.println(server);
}
}
}
测试类:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
for(int i=0;i<30;i++) {
Thread thread = new GetWeightRandomThread(i+1);
thread.start();
thread.join();
}
}
}
运行结果:
响应请求1的服务器ip是:192.168.1.107
响应请求2的服务器ip是:192.168.1.107
响应请求3的服务器ip是:192.168.1.108
响应请求4的服务器ip是:192.168.1.109
响应请求5的服务器ip是:192.168.1.103
响应请求6的服务器ip是:192.168.1.104
响应请求7的服务器ip是:192.168.1.105
响应请求8的服务器ip是:192.168.1.105
响应请求9的服务器ip是:192.168.1.105
响应请求10的服务器ip是:192.168.1.106
响应请求11的服务器ip是:192.168.1.110
响应请求12的服务器ip是:192.168.1.100
响应请求13的服务器ip是:192.168.1.101
响应请求14的服务器ip是:192.168.1.102
响应请求15的服务器ip是:192.168.1.102
响应请求16的服务器ip是:192.168.1.102
响应请求17的服务器ip是:192.168.1.102
响应请求18的服务器ip是:192.168.1.107
响应请求19的服务器ip是:192.168.1.107
响应请求20的服务器ip是:192.168.1.108
响应请求21的服务器ip是:192.168.1.109
响应请求22的服务器ip是:192.168.1.103
响应请求23的服务器ip是:192.168.1.104
响应请求24的服务器ip是:192.168.1.105
响应请求25的服务器ip是:192.168.1.105
响应请求26的服务器ip是:192.168.1.105
响应请求27的服务器ip是:192.168.1.106
响应请求28的服务器ip是:192.168.1.110
响应请求29的服务器ip是:192.168.1.100
响应请求30的服务器ip是:192.168.1.101
6.最小连接数算法
由于前面几个算法基本只考虑了请求数上的负载均衡,而没有考虑到每个请求处理时长的不同,所以也许实际并不这样。因此最小连接数算法更合适实际场景。最小连接数根据每个服务器当前连接的请求数来选择连接请求数最小的服务器。因此该算法需要为每个服务器地址维护一个连接数变量来记录当前该服务器连接的请求数,当有新请求连接,以及请求处理结束都需要改变该值。