6个负载均衡算法的总结:
RandomLoadBalance
RoundRobinLoadBalance
ActiveWeightLoadBalance
LocalFirstLoadBalance
ConsistentHashLoadBalance
ConfigurableWeightLoadBalance
AbstractLoadBalance抽象类固定流程,扩展点通过抽象方法留给子类实现
固定流程:
@Override
public Referer<T> select(Request request) {
List<Referer<T>> referers = this.referers;
if (referers == null) {
throw new MotanServiceException(this.getClass().getSimpleName() + " No available referers for call request:" + request);
}
Referer<T> ref = null;
if (referers.size() > 1) {
ref = doSelect(request);
} else if (referers.size() == 1) {
ref = referers.get(0).isAvailable() ? referers.get(0) : null;
}
if (ref != null) {
return ref;
}
throw new MotanServiceException(this.getClass().getSimpleName() + " No available referers for call request:" + request);
}
扩展点留出:
protected abstract Referer<T> doSelect(Request request);
protected abstract void doSelectToHolder(Request request, List<Referer<T>> refersHolder);
@Spi(scope = Scope.PROTOTYPE)
public interface LoadBalance<T> {
void onRefresh(List<Referer<T>> referers);
Referer<T> select(Request request);
void selectToHolder(Request request, List<Referer<T>> refersHolder);
void setWeightString(String weightString);
}
核心思想:从服务提供者列表中随机选取一个提供服务,Random 使用 AtomicLong CAS (compare-and-set)操作来更新它的seed,CAS在资源高度竞争时存在性能问题。RandomLoadBalance算法使用了ThreadLocalRandom随机类规避了Random在多线程激烈竞争下的性能问题。
ThreadLocalRandom设计有两个要点:一是通过seed是线程隔离的,不存在竞争,二是通过缓存行填充手段避免伪共享问题。
@SpiMeta(name = "random")
public class RandomLoadBalance<T> extends AbstractLoadBalance<T> {
@Override
protected Referer<T> doSelect(Request request) {
List<Referer<T>> referers = getReferers();
int idx = (int) (ThreadLocalRandom.current().nextDouble() * referers.size());
for (int i = 0; i < referers.size(); i++) {
Referer<T> ref = referers.get((i + idx) % referers.size());
if (ref.isAvailable()) {
return ref;
}
}
return null;
}
@Override
protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
List<Referer<T>> referers = getReferers();
int idx = (int) (ThreadLocalRandom.current().nextDouble() * referers.size());
for (int i = 0; i < referers.size(); i++) {
Referer<T> referer = referers.get((i + idx) % referers.size());
if (referer.isAvailable()) {
refersHolder.add(referer);
}
}
}
}
核心思想:服务提供者轮流提供服务,因为要轮流提供服务,所以该算法是有状态的,需要记录上次服务提供者。RoundRobinLoadBalance算法中使用AtomicInteger类型的idx变量记录服务提供者的服务顺序,依次递增。
@SpiMeta(name = "roundrobin")
public class RoundRobinLoadBalance<T> extends AbstractLoadBalance<T> {
private AtomicInteger idx = new AtomicInteger(0);
@Override
protected Referer<T> doSelect(Request request) {
List<Referer<T>> referers = getReferers();
int index = getNextNonNegative();
for (int i = 0; i < referers.size(); i++) {
Referer<T> ref = referers.get((i + index) % referers.size());
if (ref.isAvailable()) {
return ref;
}
}
return null;
}
@Override
protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
List<Referer<T>> referers = getReferers();
int index = getNextNonNegative();
for (int i = 0, count = 0; i < referers.size() && count < MAX_REFERER_COUNT; i++) {
Referer<T> referer = referers.get((i + index) % referers.size());
if (referer.isAvailable()) {
refersHolder.add(referer);
count++;
}
}
}
// get non-negative int
private int getNextNonNegative() {
return MathUtil.getNonNegative(idx.incrementAndGet());
}
}
核心思想:优先将负载分配给并发度低的服务提供者。
基本思想和原理:
Motan记录了服务提供者的并发度,大致记录方式如下:
incrActiveCount(request);
Response response = null;
try {
response = doCall(request);
return response;
} finally {
decrActiveCount(request, response);
}
因此可以按照服务提供者的并发度进行负载分配。同时由于Provider可能比较多,在众多的Provider中找出并发度最低的Provider,时间复杂度是O(N),为了避免N太大是对于请求响应的影响,Motan的做法是随机从Provider列表中选取一段(连续10个),在这一段中找出并发度最低的Provider进行请求的响应。
@SpiMeta(name = "activeWeight")
public class ActiveWeightLoadBalance<T> extends AbstractLoadBalance<T> {
@Override
protected Referer<T> doSelect(Request request) {
List<Referer<T>> referers = getReferers();
int refererSize = referers.size();
int startIndex = ThreadLocalRandom.current().nextInt(refererSize);
int currentCursor = 0;
int currentAvailableCursor = 0;
Referer<T> referer = null;
while (currentAvailableCursor < MAX_REFERER_COUNT && currentCursor < refererSize) {
Referer<T> temp = referers.get((startIndex + currentCursor) % refererSize);
currentCursor++;
if (!temp.isAvailable()) {
continue;
}
currentAvailableCursor++;
if (referer == null) {
referer = temp;
} else {
if (compare(referer, temp) > 0) {
referer = temp;
}
}
}
return referer;
}
@Override
protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
//略
}
private int compare(Referer<T> referer1, Referer<T> referer2) {
return referer1.activeRefererCount() - referer2.activeRefererCount();
}
static class LowActivePriorityComparator<T> implements Comparator<Referer<T>> {
@Override
public int compare(Referer<T> referer1, Referer<T> referer2) {
return referer1.activeRefererCount() - referer2.activeRefererCount();
}
}
}
核心思想:将服务节点放置在一个环上,对请求参数计算hash值,请求参数hash值相同的请求,会定位到环中相同的节点,如果实际服务提供者较少,比如只有4个,因此需要对请求参数的hash值对4进行求模,余数就是服务提供者,节点较少,极容易造成数据倾斜。因此Motan的做法是对将节点数扩大1000倍。假如有a、b、c、d四个实际服务节点,环上将会存在4个实际服务节点和4*1000-4=3996个虚拟节点,使负载分配更加均匀。
@SpiMeta(name = "consistent")
public class ConsistentHashLoadBalance<T> extends AbstractLoadBalance<T> {
private List<Referer<T>> consistentHashReferers;
@Override
public void onRefresh(List<Referer<T>> referers) {
super.onRefresh(referers);
List<Referer<T>> copyReferers = new ArrayList<Referer<T>>(referers);
List<Referer<T>> tempRefers = new ArrayList<Referer<T>>();
for (int i = 0; i < MotanConstants.DEFAULT_CONSISTENT_HASH_BASE_LOOP; i++) {
Collections.shuffle(copyReferers);
for (Referer<T> ref : copyReferers) {
tempRefers.add(ref);
}
}
consistentHashReferers = tempRefers;
}
@Override
protected Referer<T> doSelect(Request request) {
int hash = getHash(request);
Referer<T> ref;
for (int i = 0; i < getReferers().size(); i++) {
ref = consistentHashReferers.get((hash + i) % consistentHashReferers.size());
if (ref.isAvailable()) {
return ref;
}
}
return null;
}
@Override
protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
//略
}
private int getHash(Request request) {
int hashcode;
if (request.getArguments() == null || request.getArguments().length == 0) {
hashcode = request.hashCode();
} else {
hashcode = Arrays.hashCode(request.getArguments());
}
return MathUtil.getNonNegative(hashcode);
}
}
核心思想:分组权重和被分配到负载的概率是线性相关,组内采用RoundRobinLoadBalance策略。
@Override
Referer<T> next() {
//分组权重和被分配到负载的概率是线性相关
String group = randomKeyList.get(ThreadLocalRandom.current().nextInt(randomKeySize));
//按照权重找到了当前请求的服务提供组
AtomicInteger ai = cursors.get(group);
List<Referer<T>> referers = groupReferers.get(group);
//组内RoundRobinLoadBalance
return referers.get(MathUtil.getNonNegative(ai.getAndIncrement()) % referers.size());
}
weightString:表示权重配置的字符串,如groupA:3,groupB:4,groupC:5,权重配置顾名思义
MultiGroupHolder:存储分组权重信息的holder类
关键字段:
List< String > randomKeyList:存储的是分组名,分组名会有重复,分组权重越大,重复的越多,下边会举例说明。
Map
Map
配置示例:
groupA:3,groupB:4,groupC:5
经过解析,
randomKeyList=[groupA,groupA,groupA,groupB,groupB,groupB,groupB,groupC,groupC,groupC,groupC,groupC]
该列表会使用java.util.Collections.shuffle(java.util.List>)方法进行打散重新洗牌。
上边举例为各组权重最大公约数为1的情况,如果最小公倍数不是1,每组的权重需要除以最大公约数作为最终的权重。
配置示例:
groupA:2,groupB:4,groupC:8
算出2,4,8的最大公约数是2,因此实际存储时会将权重存储为1,2,4。
利用了最大公约数的两个性质:
若 r 是 a ÷ b 的余数, 则 gcd(a,b) = gcd(b,r)
a 和其倍数之最大公约数为 a。
example:
f[10,4]=f[4,10%4]=f[4,2]=f[2,4%2]=f[2,0]=2
f[3,4]=f[4,3%4]=f[4,3]=f[3,4%3]=f[3,1]=f[1,3%1]=f[1,0]=1
// 求最大公约数
private int findGcd(int n, int m) {
return (n == 0 || m == 0) ? n + m : findGcd(m, n % m);
}
// 求最大公约数
private int findGcd(int[] arr) {
int i = 0;
for (; i < arr.length - 1; i++) {
arr[i + 1] = findGcd(arr[i], arr[i + 1]);
}
return findGcd(arr[i], arr[i - 1]);
}
ConfigurableWeightLoadBalance算法中,按照参数会有三种Holder:
public void onRefresh(List<Referer<T>> referers) {
super.onRefresh(referers);
if (CollectionUtil.isEmpty(referers)) {
holder = emptyHolder;
} else if (StringUtils.isEmpty(weightString)) {
holder = new SingleGroupHolder<T>(referers);
} else {
holder = new MultiGroupHolder<T>(weightString, referers);
}
}
MultiGroupHolder核心功能:解析分组权重配置参数,填充randomKeyList(分组权重和被分配到负载的概率是线性相关具体实现)和groupReferers(每个分组的服务提供者)等数据结构。
class MultiGroupHolder<T> extends RefererListCacheHolder<T> {
private int randomKeySize = 0;
private List<String> randomKeyList = new ArrayList<String>();
private Map<String, AtomicInteger> cursors = new HashMap<String, AtomicInteger>();
private Map<String, List<Referer<T>>> groupReferers = new HashMap<String, List<Referer<T>>>();
MultiGroupHolder(String weights, List<Referer<T>> list) {
LoggerUtil.info("ConfigurableWeightLoadBalance build new MultiGroupHolder. weights:" + weights);
//weights=groupA:2,groupB:4,groupC:8
String[] groupsAndWeights = weights.split(",");
//weightsArr=[2,4,8]
int[] weightsArr = new int[groupsAndWeights.length];
//weightsMap={groupA=1,groupB=2,groupC=4}
Map<String, Integer> weightsMap = new HashMap<String, Integer>(groupsAndWeights.length);
int i = 0;
for (String groupAndWeight : groupsAndWeights) {
String[] gw = groupAndWeight.split(":");
if (gw.length == 2) {
Integer w = Integer.valueOf(gw[1]);
weightsMap.put(gw[0], w);
groupReferers.put(gw[0], new ArrayList<Referer<T>>());
weightsArr[i++] = w;
}
}
// 求出最大公约数,若不为1,每个分组的权重均除以最大公约数
int weightGcd = findGcd(weightsArr);
if (weightGcd != 1) {
for (Map.Entry<String, Integer> entry : weightsMap.entrySet()) {
weightsMap.put(entry.getKey(), entry.getValue() / weightGcd);
}
}
//最大公约数为1的情况,不需要额外处理
for (Map.Entry<String, Integer> entry : weightsMap.entrySet()) {
for (int j = 0; j < entry.getValue(); j++) {
randomKeyList.add(entry.getKey());
}
}
//重新打散洗牌
Collections.shuffle(randomKeyList);
randomKeySize = randomKeyList.size();
//每个分组的当前请求服务提供者下标初始化为0
for (String key : weightsMap.keySet()) {
cursors.put(key, new AtomicInteger(0));
}
//将服务提供者分类存储
for (Referer<T> referer : list) {
groupReferers.get(referer.getServiceUrl().getGroup()).add(referer);
}
}
@Override
Referer<T> next() {
//分组权重和被分配到负载的概率是线性相关
String group = randomKeyList.get(ThreadLocalRandom.current().nextInt(randomKeySize));
//按照权重找到了当前请求的服务提供组
AtomicInteger ai = cursors.get(group);
List<Referer<T>> referers = groupReferers.get(group);
//组内RoundRobinLoadBalance
return referers.get(MathUtil.getNonNegative(ai.getAndIncrement()) % referers.size());
}
// 求最大公约数
private int findGcd(int n, int m) {
return (n == 0 || m == 0) ? n + m : findGcd(m, n % m);
}
// 求最大公约数
private int findGcd(int[] arr) {
int i = 0;
for (; i < arr.length - 1; i++) {
arr[i + 1] = findGcd(arr[i], arr[i + 1]);
}
return findGcd(arr[i], arr[i - 1]);
}
}
核心思想:针对Consumer和Provider部署在同一台机器上的场景,可以本机直接调用,避免一次网络开销。
基本原理:
Motan是通过IP匹配的,将服务提供者中IP与Consumer的IP相同的筛选出来。
极有可能存在找不到本机服务提供者的情况,Motan的兜底措施是将负载均衡算法退化为ActiveWeightLoadBalance算法。
@SpiMeta(name = "localFirst")
public class LocalFirstLoadBalance<T> extends AbstractLoadBalance<T> {
public static final int MAX_REFERER_COUNT = 10;
public static long ipToLong(final String addr) {
final String[] addressBytes = addr.split("\\.");
int length = addressBytes.length;
if (length < 3) {
return 0;
}
long ip = 0;
try {
for (int i = 0; i < 4; i++) {
ip <<= 8;
ip |= Integer.parseInt(addressBytes[i]);
}
} catch (Exception e) {
LoggerUtil.warn("Warn ipToInt addr is wrong: addr=" + addr);
}
return ip;
}
@Override
protected Referer<T> doSelect(Request request) {
List<Referer<T>> referers = getReferers();
List<Referer<T>> localReferers = searchLocalReferer(referers, NetUtils.getLocalAddress().getHostAddress());
if (!localReferers.isEmpty()) {
referers = localReferers;
}
int refererSize = referers.size();
Referer<T> referer = null;
for (int i = 0; i < refererSize; i++) {
Referer<T> temp = referers.get(i % refererSize);
if (!temp.isAvailable()) {
continue;
}
if (referer == null) {
referer = temp;
} else {
if (compare(referer, temp) > 0) {
referer = temp;
}
}
}
return referer;
}
@Override
protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
//略
}
private List<Referer<T>> searchLocalReferer(List<Referer<T>> referers, String localhost) {
List<Referer<T>> localReferers = new ArrayList<Referer<T>>();
long local = ipToLong(localhost);
for (Referer<T> referer : referers) {
long tmp = ipToLong(referer.getUrl().getHost());
if (local != 0 && local == tmp) {
if (referer.isAvailable()) {
localReferers.add(referer);
}
}
}
return localReferers;
}
private int compare(Referer<T> referer1, Referer<T> referer2) {
return referer1.activeRefererCount() - referer2.activeRefererCount();
}
static class LowActivePriorityComparator<T> implements Comparator<Referer<T>> {
@Override
public int compare(Referer<T> referer1, Referer<T> referer2) {
return referer1.activeRefererCount() - referer2.activeRefererCount();
}
}
}