零、前戏
DividePlugin插件具有 Random、Hash、RoundRobin三种不同的负载均衡方式,本篇将主要介绍Random、Hash。
一、负载配置
- soul-examples-http修改端口,配置可以启动多个实例,分别启动8188与9188
- soul-admin的divide插件中配置负载地址
二、源码分析
- 代码核心结构
负载均衡相关类是基于模板模式,LoadBalance接口定义模板方法select并含有SPI注解。AbstractLoadBalance是其抽象类,实现select。各个具体的实现类在doSelect中实现具体负载策略。
- 负载调用的分析
DividePlugin#doExecute获取匹配的负载策略,为拼凑真实请求地址做好准备。
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
// some code ……
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
// another some code ……
return chain.execute(exchange);
}
LoadBalanceUtils#selector
通过SPI的调用方式,根据soul-admin配置的algorithm,加载不同的LoadBalance实现类。
public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) {
LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
// 调用AbstractLoadBalance#select
return loadBalance.select(upstreamList, ip);
}
spi类声名
imageAbstractLoadBalance#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);
}
// 进入实现类中的doSelect
return doSelect(upstreamList, ip);
}
- Random负载分析 -- 随机负载
@Override
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
// 计算所以两个请求地址的总权重
int totalWeight = calculateTotalWeight(upstreamList);
// 判断两个请求的地址权重是否一样
boolean sameWeight = isAllUpStreamSameWeight(upstreamList);
// 两个权重不一样,负载处理路径
if (totalWeight > 0 && !sameWeight) {
return random(totalWeight, upstreamList);
}
// 总权重为0或两权重一致,负载处理路径
return random(upstreamList);
}
两种具体random算法:
判断请求地址列表中所有的权重是否一致,如果一致,随机选取一个地址返回。
如果不一致,则计算在权重范围内偏移量权重与每个地址权重的差值,返回差值第一个大于0的,这样能保证大概率命中权重大的地址。
// 所有upstreamList元素权重不一致的random算法
private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) {
// 从0~totalWeight,生成一个偏移量
int offset = RANDOM.nextInt(totalWeight);
// 循环查找,用偏移量去减去各自权重,最终取第一个偏移量为负值的服务
for (DivideUpstream divideUpstream : upstreamList) {
offset -= getWeight(divideUpstream);
if (offset < 0) {
return divideUpstream;
}
}
return upstreamList.get(0);
}
// 所有upstreamList元素权重一致的random算法
private DivideUpstream random(final List<DivideUpstream> upstreamList) {
// 从0~upstreamList.size()-1 之间随机获取一个元素
return upstreamList.get(RANDOM.nextInt(upstreamList.size()));
}
- Hash负载分析 -- 哈希负载
Hash负载精髓是使用了ConcurrentSkipListMap数据结构, 及其tailMap(K fromKey)方法,截取Key大于等于fromKey的所有元素。
科普贴:https://www.cnblogs.com/java-zzl/p/9767255.html
@Override
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new ConcurrentSkipListMap<>();
// 循环所有地址列表,每个地址生成VIRTUAL_NODE_NUM数目的元素,放入线程安全、key有序的treeMap中
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);
}
}
// 生成ip的hash值
long hash = hash(String.valueOf(ip));
// 获取treeMap中key大于hash的,所有记录
SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
// 返回符合条件的第一条
if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey());
}
// 不存在key大于hash的记录,则返回treeMap中第一条
return treeMap.firstEntry().getValue();
}
emmm…… 权重好像没有用上,怎么负载大权重的呢???????????
三、小结
- Random、Hash的代码还是很清晰易懂,Hash是如何做到均衡分配的?要细看下hash生成的方法?Hash的权重哪去了??
- 日拱一卒
网友评论