话不多说,上来就干
上一篇 divide插件 我们了解分流插件,其中对于上游的过来的流量,进行分流
soul中的负载均衡算法包含三种算法:哈希值/随机/轮询
负载均衡
在负载均衡工具类 LoadBalanceUtils
中,最终通过接口 LoadBalance
的select(...)
方法来实现负载均衡
其中参数 upstreamList
是在网关管理后台配置的负载均衡一些参数,包括路由,比重,预热等
@SPI
public interface LoadBalance {
/**
* this is select one for upstream list.
*
* @param upstreamList upstream list
* @param ip ip
* @return divide upstream
*/
DivideUpstream select(List<DivideUpstream> upstreamList, String ip);
}
DivideUpstream
public class DivideUpstream implements Serializable {
/**
* ip
*/
private String upstreamHost;
/**
* 端口
*/
private String protocol;
/**
* 路由
*/
private String upstreamUrl;
/**
* 负载均衡比重
*/
private int weight;
/**
* 策略是否打开
*/
@Builder.Default
private boolean status = true;
/**
* 开始时间
*/
private long timestamp;
/**
* 预热
*/
private int warmup;
}
这个接口被@SPI
修饰,表明是一个通过服务发现机制来实现的接口,这个接口的具体实现类是在运行时动态加载到内存的,在 META-INF.SOUL
包下,有一个spi的接口文件,里面放的就是这个接口的具体实现类,java通过这个文件,可以在运行时,通过动态加载的方式,加载实现类
random=org.dromara.soul.plugin.divide.balance.spi.RandomLoadBalance
roundRobin=org.dromara.soul.plugin.divide.balance.spi.RoundRobinLoadBalance
hash=org.dromara.soul.plugin.divide.balance.spi.HashLoadBalance
使用模板方法,在抽象类中 AbstractLoadBalance
定义了模板流程
public abstract class AbstractLoadBalance implements LoadBalance {
/**
* Do select divide upstream.
*
* @param upstreamList the upstream list
* @param ip the ip
* @return the divide upstream
*/
protected abstract DivideUpstream doSelect(List<DivideUpstream> upstreamList, String ip);
@Override
public DivideUpstream select(final List<DivideUpstream> upstreamList, final String ip) {
if (CollectionUtils.isEmpty(upstreamList)) {
return null;
}
if (upstreamList.size() == 1) {
return upstreamList.get(0);
}
return doSelect(upstreamList, ip);
}
protected int getWeight(final DivideUpstream upstream) {
if (!upstream.isStatus()) {
return 0;
}
int weight = getWeight(upstream.getTimestamp(), getWarmup(upstream.getWarmup(), Constants.DEFAULT_WARMUP), upstream.getWeight());
return weight;
}
private int getWeight(final long timestamp, final int warmup, final int weight) {
if (weight > 0 && timestamp > 0) {
int uptime = (int) (System.currentTimeMillis() - timestamp);
if (uptime > 0 && uptime < warmup) {
return calculateWarmupWeight(uptime, warmup, weight);
}
}
return weight;
}
private int getWarmup(final int warmup, final int defaultWarmup) {
if (warmup > 0) {
return warmup;
}
return defaultWarmup;
}
private int calculateWarmupWeight(final int uptime, final int warmup, final int weight) {
int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}
}
最终还是通过抽象类的实现类,重写doSelect(...)
方法,来实现具体的负载均衡算法
哈希值
直接上代码
@Override
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
//构造一个自增长/自排序的map
final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new ConcurrentSkipListMap<>();
for (DivideUpstream address : upstreamList) {
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
long addressHash = hash("SOUL-" + address.getUpstreamUrl() + "-HASH-" + i);
//将所有路由都md5编码加入到map中
treeMap.put(addressHash, address);
}
}
//访问ip加密,作为获取部分元素的key
long hash = hash(String.valueOf(ip));
// 获取自增map大于 hash 的map集合
SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
if (!lastRing.isEmpty()) {
// 截取的map不为空,返回截取后的map的第一个元素的值
return lastRing.get(lastRing.firstKey());
}
// 截取后的map为空,返回整个原始自增map的第一个元素的值
return treeMap.firstEntry().getValue();
}
private static long hash(final String key) {
// md5 byte
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new SoulException("MD5 not supported", e);
}
md5.reset();
byte[] keyBytes;
keyBytes = key.getBytes(StandardCharsets.UTF_8);
md5.update(keyBytes);
// 获取摘要
byte[] digest = md5.digest();
// hash code, Truncate to 32-bits
// 转换32位
long hashCode = (long) (digest[3] & 0xFF) << 24
| ((long) (digest[2] & 0xFF) << 16)
| ((long) (digest[1] & 0xFF) << 8)
| (digest[0] & 0xFF);
return hashCode & 0xffffffffL;
}
随机
直接上代码
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
// 计算总的比重,因为未必总的比重是100%
int totalWeight = calculateTotalWeight(upstreamList);
// 判断是否所有节点都是一样的比重
boolean sameWeight = isAllUpStreamSameWeight(upstreamList);
if (totalWeight > 0 && !sameWeight) {
// 随机获取
return random(totalWeight, upstreamList);
}
// If the weights are the same or the weights are 0 then random
// 如果几个节点比重一致,则直接随机算法获取其中一个
return random(upstreamList);
}
private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) {
// If the weights are not the same and the weights are greater than 0, then random by the total number of weights
// 获取总比重中的随机值
int offset = RANDOM.nextInt(totalWeight);
// Determine which segment the random value falls on
// 比较比重,选择具体的分流规则
for (DivideUpstream divideUpstream : upstreamList) {
offset -= getWeight(divideUpstream);
if (offset < 0) {
return divideUpstream;
}
}
return upstreamList.get(0);
}
轮询
直接上代码
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
String key = upstreamList.get(0).getUpstreamUrl();
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
if (map == null) {
//第一次进来初始化map
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
map = methodWeightMap.get(key);
}
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
long now = System.currentTimeMillis();
DivideUpstream selectedInvoker = null;
WeightedRoundRobin selectedWRR = null;
for (DivideUpstream upstream : upstreamList) {
String rKey = upstream.getUpstreamUrl();
WeightedRoundRobin weightedRoundRobin = map.get(rKey);
int weight = getWeight(upstream);
if (weightedRoundRobin == null) {
weightedRoundRobin = new WeightedRoundRobin();
weightedRoundRobin.setWeight(weight);
map.putIfAbsent(rKey, weightedRoundRobin);
}
if (weight != weightedRoundRobin.getWeight()) {
//weight changed
weightedRoundRobin.setWeight(weight);
}
long cur = weightedRoundRobin.increaseCurrent();
weightedRoundRobin.setLastUpdate(now);
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = upstream;
selectedWRR = weightedRoundRobin;
}
totalWeight += weight;
}
// 获取自旋锁
// 更新 methodWeightMap
if (!updateLock.get() && upstreamList.size() != map.size() && updateLock.compareAndSet(false, true)) {
try {
// copy -> modify -> update reference
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<>(map);
newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > recyclePeriod);
methodWeightMap.put(key, newMap);
} finally {
//释放自旋锁
updateLock.set(false);
}
}
if (selectedInvoker != null) {
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
return upstreamList.get(0);
}