目录
背景
一、轮询算法
实现原理
二、随机算法
实现原理
三、哈希算法
实现原理
四、权重算法
实现原理
五、使用单元测试
轮询
随机
哈希
权重
随着大数据库时代的来临,我们现在很多分布式应用都使用到了集群,每个集群离不开负载均衡,也就是说通过负载均衡算法将流量瓜分, 每个机器的负载也会得到降低。
轮询是将所有请求依次访问到集群的服务器上,每个服务轮流接收请求,不管每台服务器的性能好坏,都能公平地接收到请求。
利用AtomicLong 来实现原子性,volatile保证了可见性,只要有线程进来,更改index值后,其他线程会及时地发现,简单地说线程不会重用一个index。
getAndIncrement()方法保证了id持续递增,因此能够实现对list列表的轮询。
static class LoadBalance {
private static volatile AtomicLong index = new AtomicLong(0);
private static Object getOne(List extends Object> list) {
if (index.get() >=Integer.MAX_VALUE) {
// 当达到整型最大值是,归0
index.set(0);
}
return list.get((int) index.getAndIncrement() % list.size());
}
}
随机算法也是一种常用的负载均衡算法, 它能够保证在大数据量的情况下集群的机器能够大约的相等接收相等量的请求,也就是说请求进入到进群里的每个机器的概率是相等的。
使用nextInt()方法获取到小于指定整数中的随机一个值,比如nextInt(5), 那么获取到0-4之间任意一个数值的概率是相等的, 因此获取到list列表中的每个元素的概率也是相等的。
static class LoadBalance {
private static Random random = new Random(System.currentTimeMillis());
private static Object getOneByRandom(List extends Object> list) {
return list.get(random.nextInt(list.size()));
}
}
哈希算法是一种散列算法,符合均匀分布的原则,每个元素通过计算出来的hash值来找到hash表里的一个位置,在大数据量下能均匀散射在hash表里,也就是说通过哈希算法能够让请求均匀地分布到集群里的每台机器上。
实际开发中, 每个请求是唯一的,可以看成一个唯一的id, 根据id来获取hashcode,与服务器列表取模,取到的模值就是选到的机器。
把每个线程理解成一个请求, 然后用indexAtomic标识唯一性。
private static AtomicLong indexAtomic = new AtomicLong(0);
@Test
public void loadBalanceHash() {
// 哈希
for (int i = 0; i < SERIES_SIZE; i++) {
if (indexAtomic.get() >= Integer.MAX_VALUE) {
indexAtomic.set(0);
}
threadPool.submit(() -> {
String serverName = (String) LoadBalance.getOneByHash(serverLists, indexAtomic.getAndIncrement());
result.add(serverName);
});
}
System.out.println("================哈希算法================");
print(result);
}
然后把拿到的值计算hashCode(), 当计算得到的值小于0时,那么就取绝对值。
private static Object getOneByHash(List extends Object> list, Object arg) {
int value = arg.hashCode();
if (value < 0) {
value = Math.abs(value);
}
return list.get(value % list.size());
}
权重的意思是分值,可以理解成每台机器得到的分值,如果某台机器的分值大,那么就相当于能够接收到更多的请求, nginx服务器里可以将流量分发到不同的机器,并设置权重比,例如分发到A、B两台机器,比重为2:1, 那么A服务器接收到的请求量平均是B服务器接收到的请求量的两倍。
权重算法有点能者多劳的味道,适用于集群中配置不相等的情况下, 高配置的服务器多分担一点,配置低的服务器少分担一点。
将权重比看成分数值不同的区间,每个区间对应一台服务器, 每次访问前随机生成一个在区间内的值,该值在哪个区间,那么就返回哪台服务器。
转换成区间:
public void loadBalanceWeight() {
// 权重, 设置三台机器的权重为 3:1:1
// 4. 权重算法, 给每台机器分配权重,假设分配权重为第一台机器为3,其他机器都为1,那么weight为[3,4,5,6]
int[] weight = new int[serverLists.size() + 1];
weight[0] = 0;
int length;
for (int i = 1; i < weight.length; i++) {
if (i == 1) {
// 分配第一台机器权重为3
weight[i] = 3;
} else {
// 其他机器为1
weight[i] = weight[i - 1] + 1;
}
}
length = weight[weight.length - 1];
final int temp = length;
for (int i = 0; i < SERIES_SIZE; i++) {
threadPool.submit(() -> {
String serverName = (String) LoadBalance.getOneByWeight(serverLists, weight, temp);
result.add(serverName);
});
}
System.out.println("================权重算法================");
print(result);
}
查看随机值在哪个区间, 如果在[0,3)那么返回A服务器,如果在[3,4) 区间返回B服务器,如果在[4,5) 之间返回C服务器。
private static Object getOneByWeight(List extends Object> list, int[] weight, Integer length) {
int target = random.nextInt(length);
// 计算target值对应出现在第几个位置 ,对应的在list中的位置也是第几个。
for (int i = 0; i < weight.length; i++) {
int start = weight[i], end = weight[i + 1];
if (target >= start && target < end) {
return list.get(i);
}
}
return list.get(0);
}
package com.example.producer;
import com.example.producer.util.ThreadPoolUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LoadBalanceTest {
private static AtomicLong indexAtomic = new AtomicLong(0);
private static final int SERIES_SIZE = 10000;
private static final String path = "D:\\idea project\\rocketmq-project\\producer\\src\\test\\java\\com\\example\\producer\\indexFile";
private static ReadWriteLock lock = new ReentrantReadWriteLock();
List serverLists = new ArrayList<>();
ExecutorService threadPool = null;
List result = null;
@Before
public void initList() {
threadPool = ThreadPoolUtil.getThreadPool();
serverLists.add("serverA");
serverLists.add("serverB");
serverLists.add("serverC");
List init = new ArrayList<>();
result = Collections.synchronizedList(init);
}
@After
public void clearList() {
serverLists.clear();
result.clear();
threadPool.shutdown();
}
@Test
public void loadBalancePolling() {
// 轮询
for (int i = 0; i < SERIES_SIZE; i++) {
threadPool.submit(() -> {
String serverName = (String) LoadBalance.getOne(serverLists);
result.add(serverName);
});
}
System.out.println("================轮询算法================");
print(result);
}
@Test
public void loadBalanceRandom() {
// 随机
for (int i = 0; i < SERIES_SIZE; i++) {
threadPool.submit(() -> {
String serverName = (String) LoadBalance.getOneByRandom(serverLists);
result.add(serverName);
});
}
System.out.println("================随机算法================");
print(result);
}
@Test
public void loadBalanceHash() {
// 哈希
for (int i = 0; i < SERIES_SIZE; i++) {
threadPool.submit(() -> {
String serverName = (String) LoadBalance.getOneByHash(serverLists, indexAtomic.getAndIncrement());
result.add(serverName);
});
}
System.out.println("================哈希算法================");
print(result);
}
@Test
public void loadBalanceWeight() {
// 权重
// 4. 权重算法, 给每台机器分配权重,假设分配权重为第一台机器为3,其他机器都为1,那么weight为[3,4,5,6]
int[] weight = new int[serverLists.size() + 1];
weight[0] = 0;
int length;
for (int i = 1; i < weight.length; i++) {
if (i == 1) {
// 分配第一台机器权重为3
weight[i] = 3;
} else {
// 其他机器为1
weight[i] = weight[i - 1] + 1;
}
}
length = weight[weight.length - 1];
final int temp = length;
for (int i = 0; i < SERIES_SIZE; i++) {
threadPool.submit(() -> {
String serverName = (String) LoadBalance.getOneByWeight(serverLists, weight, temp);
result.add(serverName);
});
}
System.out.println("================权重算法================");
print(result);
}
private static void print(List result) {
int countA = 0, countB = 0, countC = 0;
try {
lock.readLock().lock();
if (!result.isEmpty()) {
for (String s : result) {
if ("serverA".equalsIgnoreCase(s)) {
countA++;
} else if ("serverB".equalsIgnoreCase(s)) {
countB++;
} else {
countC++;
}
}
System.out.println("countA=" + countA + ",countB=" + countB + ",countC=" + countC);
}
} finally {
lock.readLock().unlock();
}
}
static class LoadBalance {
private static volatile AtomicInteger index = new AtomicInteger(0);
private static Random random = new Random(System.currentTimeMillis());
private static Object getOne(List extends Object> list) {
if (index.get() == Integer.MAX_VALUE) {
index.set(0);
}
return list.get(index.getAndIncrement() % list.size());
}
private static Object getOneByRandom(List extends Object> list) {
return list.get(random.nextInt(list.size()));
}
private static Object getOneByHash(List extends Object> list, Object arg) {
int value = arg.hashCode();
if (value < 0) {
value = Math.abs(value);
}
return list.get(value % list.size());
}
private static Object getOneByWeight(List extends Object> list, int[] weight, Integer length) {
int target = random.nextInt(length);
// 计算target值对应出现在第几个位置 ,对应的在list中的位置也是第几个。
for (int i = 0; i < weight.length; i++) {
int start = weight[i], end = weight[i + 1];
if (target >= start && target < end) {
return list.get(i);
}
}
return list.get(0);
}
}
}