这是我参与8月更文挑战的第25天,活动详情查看: 8月更文挑战
本文使用源码地址:simple-rpc
负载均衡的目的是将请求按照某种策略分布到多台机器上,使得系统能够实现横向扩展。分布式服务框架中实现负载均衡是通过软件算法来完成的。
在分布式服务框架中,负载均衡是服务端实现的,其实现原理如下:
- 服务消费端在引用启动之初从服务注册中心获取服务提供者列表,缓存到服务调用端本地缓存。
- 服务消费端发起服务调用之前,先通过某种策略或者算法从服务提供者列表本地缓存中选择本次调用的目标机器,再发起服务调用,从而完成负载均衡的功能。
负载均衡算法
提供如下接口,接口入参为服务提供者列表。
public interface LoadBalanceStrategy {
Provider select(List<Provider> providers);
}
加权随机算法
首先根据加权数放大服务提供者列表,比如服务提供者A加权数为3,放大之后变为A,A,A,存放在新的服务提供者列表中,获取服务提供者列表大小区间之间的随机数,作为服务提供者了列表的索引来获取服务。代码如下:
public class WeightRandomLoadBalanceStrategyImpl implements LoadBalanceStrategy {
@Override
public Provider select(List<Provider> providers) {
for (Provider provider : providers) {
int weight = provider.getWeight();
for (int i = 0; i < weight; i++) {
providers.add(Provider.copy(provider));
}
}
int maxLen = providers.size();
int random = RandomUtils.nextInt(0, maxLen - 1);
return providers.get(random);
}
}
加权轮询算法
依次按顺序获取服务提供者列表中的数据,并使用计数器记录使用过的数据索引,若索引到最后一个数据,则计数器归0,重新开始循环。代码如下:
public class WeightPollingLoadBalanceStrategyImpl implements LoadBalanceStrategy {
private final AtomicInteger index = new AtomicInteger(0);
private final Lock lock = new ReentrantLock();
@Override
public Provider select(List<Provider> providers) {
for (Provider provider : providers) {
int weight = provider.getWeight();
for (int i = 0; i < weight; i++) {
providers.add(Provider.copy(provider));
}
}
lock.lock();
try {
if (index.get() >= providers.size()) {
index.set(0);
}
Provider provider = providers.get(index.getAndIncrement());
if (null == provider) {
return providers.get(0);
}
return provider;
} finally {
lock.unlock();
}
}
}
源地址hash算法
利用请求来源的IP地址取hash值,再根据服务提供者列表大小取模,得到服务提供者列表索引,进而获取到服务提供者。代码如下:
public class HashLoadBalanceStrategyImpl implements LoadBalanceStrategy {
@Override
public Provider select(List<Provider> providers) {
int hashCode = IpUtil.getLocalIP().hashCode();
return providers.get(hashCode % providers.size());
}
}
负载均衡引擎
我们需要将以上算法实现整合到我们的分布式服务框架实现中去,因此我们定义一个负载均衡引擎类,使用门面模式,对外提供统一的API,根据不同的策略配置来选取不同的策略服务。策略引擎代码实现如下:
public static LoadBalanceStrategy getLoadBalanceStrategy(String loadBalanceStrategy) {
LoadBalanceStrategyEnum strategyEnum = LoadBalanceStrategyEnum.getByCode(loadBalanceStrategy);
if (strategyEnum == null) {
log.debug("can't find strategy with property loadBalanceStrategy = {}", loadBalanceStrategy);
throw new SRpcException("can't find strategy with property loadBalanceStrategy:" + loadBalanceStrategy);
} else {
return LOAD_BALANCE_STRATEGY_MAP.get(strategyEnum);
}
}
小结
我们提供了几种简单的负载均衡算法,以及策略引擎来方便使用。但是在真实环境中远没有这么简单,在常见的开源框架中一般会有一致性hash,最少活跃调用数算法等等这种更加复杂的算法。总体来说目标是一致的,都是希望将流量打散到服务提供者机器上。