首先查看 LoadBalance 接口
Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException;
LoadBalance 定义了一个方法就是从 invokers 列表中选取一个
AbstractLoadBalance 抽象类是所有负载均衡策略实现类的父类,实现了LoadBalance接口 的方法,同时提供抽象方法交由子类实现,
public Invoker select(List> invokers, URL url, Invocation invocation) {
if (invokers == null || invokers.size() == 0)
return null;
if (invokers.size() == 1)
return invokers.get(0);
return doSelect(invokers, url, invocation);
}
protected abstract Invoker doSelect(List> invokers, URL url, Invocation invocation);
protected Invoker doSelect(List> invokers, URL url, Invocation invocation) {
int length = invokers.size();//总个数
int totalWeight = 0; //最大权重
boolean sameWeight = true; //权重是否一样
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight; //累计总权重
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false; //计算所有权重是否一样
}
}
//循环检测,如果offset小于0,则返回该服务
if (totalWeight > 0 && ! sameWeight) {
//当权重不相同且权重大于0则按总权重数随机。
int offset = random.nextInt(totalWeight);
//确定随机值落在那个片段上
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}
//如果权重相同或者权重为0则均等随机
return invokers.get(random.nextInt(length));
}
栗子:结合代码
3个服务 A,B,C 其中A的权重为1,B的权重为2,C的权重为3,那么invokers中的服务的顺序有6种情况,分别为ABC,BAC,ACB,CAB,BCA,CBA
此时的 totalWeight=1+2+3=6,此时offset=random.nextInt(totalWeight)=random.nextInt(6)=【0,1,2,3,4,5】中某个随机数
offset | offset | offset | offset | offset | offset | |
invokers | 0 | 1 | 2 | 3 | 4 | 5 |
ABC | A | B | B | C | C | C |
BAC | B | B | A | C | C | C |
ACB | A | C | C | C | B | B |
CAB | C | C | C | A | B | B |
BCA | B | B | C | C | C | A |
CBA | C | C | C | B | B |
文字描述:
当offset=0的时候,如果invokers=ABC 则调用的是A,因为0-1<0 ; 当offset=4的时候,会经过几个循环
①offset=4-1=3>0,此时offset=3,继续下一步
②offset=3-2=1>0,此时offset=1,继续下一步
③offset=1-3=-2<0,调用 此时调用C
1)获取轮询key 服务名+方法名
获取可供调用的invokers个数length
设置最大权重的默认值maxWeight=0
设置最小权重的默认值minWeight=Integer.MAX_VALUE
2)遍历所有Inokers,比较出得出maxWeight和minWeight
3)如果权重是不一样的
根据key获取自增序列
自增序列加一与最大权重取模默认得到currentWeigth
遍历所有invokers筛选出大于currentWeight的invokers
设置可供调用的invokers的个数length
4)自增序列加一并与length取模,从invokers获取invoker
public class RoundRobinLoadBalance extends AbstractLoadBalance {
public static final String NAME = "roundrobin";
private final ConcurrentMap sequences = new ConcurrentHashMap();
protected Invoker doSelect(List> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName(); //①获取轮询key(服务名+方法名)
int length = invokers.size(); // Number of invokers ②获取可供调用的invokers个数
int maxWeight = 0; // The maximum weight 最大权重值 ③设置最大权重的默认值为0
int minWeight = Integer.MAX_VALUE; // The minimum weight 最小权重支持,默认最大
final LinkedHashMap, IntegerWrapper> invokerToWeightMap = new LinkedHashMap, IntegerWrapper>();
//权重值总数
int weightSum = 0;
for (int i = 0; i < length; i++) {//④遍历invokers,比较找出maxWeight和minWeight
//获取权重值
int weight = getWeight(invokers.get(i), invocation);
//获取最大和最小权重值
maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
minWeight = Math.min(minWeight, weight); // Choose the minimum weight
if (weight > 0) {
invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
weightSum += weight;
}
}
//⑤如果权重不一样
AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
//⑥根据key获取自增序列
sequence = sequences.get(key);
}
int currentSequence = sequence.getAndIncrement();
if (maxWeight > 0 && minWeight < maxWeight) {
//轮询当前值
//⑦自增序列加一与最大权重取模默认得到currentWeigth
int mod = currentSequence % weightSum;
for (int i = 0; i < maxWeight; i++) {
//轮询当前值的余数轮询从服务中获取
for (Map.Entry, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
final Invoker k = each.getKey();
final IntegerWrapper v = each.getValue();
if (mod == 0 && v.getValue() > 0) {
return k;
}
if (v.getValue() > 0) {
v.decrement();
mod--;
}
}
}
}
// Round robin
//直接轮询找服务
return invokers.get(currentSequence % length);//自增序列加一并与length取模,从invokers获取invoker
}
private static final class IntegerWrapper {
private int value;
public IntegerWrapper(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public void decrement() {
this.value--;
}
}
}
public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = "leastactive";
private final Random random = new Random();
protected Invoker doSelect(List> invokers, URL url, Invocation invocation) {
int length = invokers.size(); // 服务提供者总个数
int leastActive = -1; // 最小的活跃数
int leastCount = 0; // 相同最小活跃数的个数
int[] leastIndexs = new int[length]; // 相同最小活跃数的下标
int totalWeight = 0; // 总权重
int firstWeight = 0; // 第一个权重,用户计算是否相同
boolean sameWeight = true; //是否所有权重相同
for (int i = 0; i < length; i++) {
Invoker invoker = invokers.get(i);
int active = RpcStatus.getStatus(invoker.getUrl(),
invocation.getMethodName()).getActive(); // 每个服务的活跃数
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // 权重
if (leastActive == -1 || active < leastActive) { //发现最小的活跃数,重新开始
leastActive = active; // 记录当前活跃数(最小活跃数)
leastCount = 1; // 重新统计相同最小活跃数的个数
leastIndexs[0] = i; // 最小活跃数调的下标 坐标id
totalWeight = weight; // 权重
firstWeight = weight; // 第一个权重
sameWeight = true; // 重新设置为权重相同
} else if (active == leastActive) { // 如果活跃数相同,则记录权重
leastIndexs[leastCount++] = i; // 累计相同最小活跃数下标
totalWeight += weight; //累计总权重
// If every invoker has the same weight?
if (sameWeight && i > 0
&& weight != firstWeight) {
sameWeight = false;
}
}
}
// assert(leastCount > 0)
if (leastCount == 1) {
// If we got exactly one invoker having the least active value, return this invoker directly.
return invokers.get(leastIndexs[0]);//如果只有一个最小则直接返回
}
//如果存在活跃数相关的服务,则随机一个权重值,看看权重值落到哪个服务中则返回某个服务
if (!sameWeight && totalWeight > 0) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
//如果权重不相同且权重大于0则按总权重数随机
int offsetWeight = random.nextInt(totalWeight);
// 确定随机值落在哪个片断上
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
//如果权重相同或权重为0则均等随机
return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
}
例子.每个服务有一个活跃计数器
,那么我们假如有A,B两个提供者.计数均为0.当A提供者开始处理请求,该计数+1,此时A还没处理完,当处理完后则计数-1.而B请求接收到请求处理得很快.B处理完后A还没处理完,所以此时A,B的计数为1,0.那么当有新的请求来的时候,就会选择B提供者(B的活跃计数比A小)
算法分析:
假设A,B,C,节点的最小活跃数分别是1,1,2权重为1,2,3,则leastIndexs
(该数组是最小活跃数组)数组内容为[A,B].A,B的权重是1和2,所以调用A的概率为 1/(1+2) = 1/3,B的概率为 2/(1+2) = 2/3。
其中活跃数的变化是在com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
中,如果没有配置dubbo:reference
的actives
属性,默认是调用前活跃数+1,调用结束-1
在业务开发中,我们常把数据持久化到数据库中。如果需要读取这些数据,除了直接从数据库中读取外,为了减轻数据库的访问压力以及提高访问速度,我们更多地引入缓存来对数据进行存取。读取数据的过程一般为:
1.这里通过描述图片加入缓存的数据读取过程
对于分布式缓存,不同机器上存储不同对象的数据。为了实现这些缓存机器的负载均衡,可以使用式子1来定位对象缓存的存储机器:
m = hash(图片名称) mod n ——式子1
存在问题:当服务器数量发生变化时,会引起缓存雪崩,可能引起整体系统压力过大而崩溃;即便不发生上述情况,缓存的位置几乎都会发生改变,因此需要考虑怎么才能尽量减少受影响的缓存呢?
它也是通过取模的方法,但是不是对服务器的数量进行,而是对2^32取模。还是以咱们得到三台服务器为例,这次hash(服务器的IP地址) % 2^32,计算得到的结果一定是在0~2^32-1之间的某个整数,此时就用这个结果数代表服务器,那么三台服务器可以入如下图表示;
当我们需要缓存照片时,仍然使用图片名称作为key,计算hash(图片名称)%2^32,那么可以用橘色的点表示,但是到底缓存到哪台服务器上呢?A 为什么?因为从图片位置顺时针最先遇到的服务器就是A,因此将被缓存到服务器A上。
一致性哈希算法:
优点:当某台服务器出现down机,那么原来缓存在服务器B上的图片将被缓存至服务器C中,其中只有原来服务器B缓存的照片位置发生改变,其他照片未改动。
缺陷:hash环的偏斜---->我们理想化的吧三台服务器均匀的映射到hash环上,无奈理想很丰满,显示太骨感,想象的和实际情况往往反差很大。
实际上很有可能出现下面右图的情况:1,2,3,4,6图片均被混村到服务器A上了,服务器B上只有5图片,C服务器上压根没有缓存上图片,这就不开心了。如何解决这样的问题呢?一致性哈希算法提出了“虚拟节点”来解决
怎么能均匀分布到真hash环上呢,主要咱们只有三个服务器,没有那么多节点可以分布,想到了分身,记得大话西游牛魔王对打孙悟空,都是牛虱和猴子猴孙的PK,既然没有真正多余的物理服务器节点,那么只能讲现有的物理节点通过虚拟的方法复制出来,成为虚拟节点,加入到hash环中。
“虚拟节点”是“实际节点”(物理服务器)在hash环上的复制品,一个实际节点能对应过个虚拟节点。引入的虚拟节点越多,混村的分布月均衡。