一、目标
1、搞清楚Soul网关负载均衡实现原理;
二、内容
2.1 背景
-
负载均衡:负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性;
-
常用负载均衡算法:
- **Round Robin(轮询):**为第一个请求选择列表中的第一个服务器,然后按顺序向下移动列表直到结尾,然后循环。
- **Least Connections(最小连接):**优先选择连接数最少的服务器,在普遍会话较长的情况下推荐使用。
- **Hash:**根据请求源的 IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。
-
Soul网关支持的负载均衡算法:
1、Random
2、Hash
3、Round Robin
- Divide插件使用:Soul源码解析(2)-Soul单机部署及功能体验
2.2 负载均衡源码分析
2.2.1 AbstractLoadBalance类
-
AbstractLoadBalance类是soul网关支持的负载均衡算法的基类,实现了LoadBalance接口,重写select方法,提供了抽象方法doSelect,负载均衡的具体实现类会重写doSelect方法 ;
-
LoadBalance接口如下,传入IP(你请求的真实IP)和一个DivideUpstream集合,最后返回一个DivideUpstream对象:
@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对象包括:
@Data
@ToString
@Builder
public class DivideUpstream implements Serializable {
// 存活的IP
private String upstreamHost;
//协议
private String protocol;
//请求URL
private String upstreamUrl;
//权重
private int weight;
//开闭状态,默认true
@Builder.Default
private boolean status = true;
//开始时间
private long timestamp;
//预热时间(ms)
private int warmup;
}
-
接下来看AbstractLoadBalance类
public abstract class AbstractLoadBalance implements LoadBalance { //负载均衡的具体实现类 protected abstract DivideUpstream doSelect(List<DivideUpstream> upstreamList, String ip); //重写LoadBalance接口里面的select方法 @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); } } -
可以在下图的位置选择负载均衡策略:

2.2.2 RandomLoadBalance分析
RandomLoadBalance是随机负载均衡算法的实现类,它继承了AbstractLoadBalance方法,重写了doSelect()方法。这里重点看一下里面的随机负载均衡的实现方法:
private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) {
// 如果权重不相同且权重大于0,则按权重总数随机
int offset = RANDOM.nextInt(totalWeight);
// 确定随机值落在哪个段上
for (DivideUpstream divideUpstream : upstreamList) {
offset -= getWeight(divideUpstream);
if (offset < 0) {
return divideUpstream;
}
}
return upstreamList.get(0);
}
2.2.3 HashLoadBalance分析
HashLoadBalance是根据请求源的 IP 的散列(hash)来选择要转发的服务器,同样继承了AbstractLoadBalance方法,重写了doSelect()方法。核心算法实现如下:
@Join
public class HashLoadBalance extends AbstractLoadBalance {
private static final int VIRTUAL_NODE_NUM = 5;
@Override
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
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);
treeMap.put(addressHash, address);
}
}
long hash = hash(String.valueOf(ip));
SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey());
}
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
long hashCode = (long) (digest[3] & 0xFF) << 24
| ((long) (digest[2] & 0xFF) << 16)
| ((long) (digest[1] & 0xFF) << 8)
| (digest[0] & 0xFF);
return hashCode & 0xffffffffL;
}
}
2.2.4 RoundRobinLoadBalance分析
RoundRobinLoadBalance为第一个请求选择列表中的第一个服务器,然后按顺序向下移动列表直到结尾,然后循环。结构跟上面两个都一样,核心实现如下:
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) {
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;
}
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);
}
三、总结
今天一起学习了Soul网关三种不同的负载均衡实现方法,这里使用了抽象模板设计模式,在子类里面实现了各自不同的负载计算方法。结合例子自己跑一遍代码,会更清楚整个流程。
本文详细解读了Soul网关中的负载均衡实现,涉及RoundRobin、Hash和Random三种算法,通过AbstractLoadBalance模板,展示了它们在AbstractLoadBalance、RandomLoadBalance、HashLoadBalance和RoundRobinLoadBalance中的具体实现。
-Soul网关负载均衡源码解读&spm=1001.2101.3001.5002&articleId=113371875&d=1&t=3&u=58ee47dc0a24492f93cf8078dc3006c2)
1889

被折叠的 条评论
为什么被折叠?



