前言
前面我们对Ribbon负载均衡模块的五大组件进行了简单的概述,但大部分情况这五个组件相互配合工作才能实现负载均衡的能力,而今天我们说的ILoadBalancer
说白了就是对这些组件组合的一个容器。
ILoadBalancer接口
核心方法
该接口可以说是Ribbon负载均衡器最核心的一个接口,提供了对服务器操作的一组方法。
public void addServers(List<Server> newServers);
public Server chooseServer(Object key);
public void markServerDown(Server server);
public List<Server> getReachableServers();
public List<Server> getAllServers();
addServers:
添加一组服务器chooseServer:
根据key从负载均衡中获取一个服务器markServerDown:
标记一个服务器挂了getAllServers:
获取所有的服务器,包括可访问的不可访问的getReachableServers:
获取所有可用的服务器
继承关系
AbstractLoadBalancer:
最主要就是新增了一个获取负载均衡指标管理的方法LoadBalancerStats getLoadBalancerStats()
意为着子类可以根据服务的状态来获取健康的服务器NoOpLoadBalancer:
从名字可以看出 没有任何操作的 所有的方法都返回的是nullBaseLoadBalancer:
最基础的负载均衡器,注意不是一个抽象类,意为着你可以直接拿过来使用。但是服务列表服务动态化,无法做到服务过滤,无法做到区域识别DynamicServerListLoadBalancer:
在BaseLoadBalancer
的基础上增加了可以动态的获取服务列表的功能ZoneAwareLoadBalancer:
区域意识(服务之间的调用希望尽量是同区域进行的,减少延迟)
在SpringCloud环境中 SC的开发者并没有对ILoadBalancer做任何扩展,都是基于上面这些ILoadBalancer。
BaseLoadBalancer
这个负载均衡器在实际的开发过程中 使用的不是很多 但是作为理解ILoadBalancer 是必不可少的,它提供了最最基础的负载均衡能力,所以你直接使用是没有任何问题的,在有些时候其实直接使用它也是比较好的选择。所有的负载均衡都是在此基础上进行扩展的。
重要的成员变量
protected IRule rule = DEFAULT_RULE;
protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;
protected IPing ping = null;
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
protected volatile List<Server> upServerList = Collections
.synchronizedList(new ArrayList<Server>());
protected String name = DEFAULT_NAME;
protected Timer lbTimer = null;
protected int pingIntervalSeconds = 10;
protected int maxTotalPingTimeSeconds = 5;
protected AtomicBoolean pingInProgress = new AtomicBoolean(false);
protected LoadBalancerStats lbStats;
private IClientConfig config;
private List<ServerListChangeListener> changeListeners = new CopyOnWriteArrayList<ServerListChangeListener>();
private List<ServerStatusChangeListener> serverStatusListeners = new CopyOnWriteArrayList<ServerStatusChangeListener>();
rule:
我们之前聊过的 五大组件之一的IRule
,默认为RoundRobinRule
线性轮询策略pingStrategy:
ping策略 内部是使用IPing
来实现ping的功能。默认是SerialPingStrategy
线性规则。如果我们的服务器很多 这里的耗时就会很明显 所以你可以自定义并行的ping策略。ping:
之前聊过的 五大组件的另一个组件IPing
可以通过程序和配置文件来设置allServerList:
所有的服务器列表upServerList:
可用的服务器列表lbTimer:
定时器 用于启动IpingTask
定时使用IPing
去检查server的isAlive状态的pingIntervalSeconds:
ping间隔时间,默认30s 可以通过配置<clientName>.ribbon.NFLoadBalancerPingInterval
来指定maxTotalPingTimeSeconds:
每次Ping的最长时间 通过<clientName>.ribbon.NFLoadBalancerMaxTotalPingTime
来配置,但是没有一个地方用到这个值?pingInProgress:
标识ping是否正在进行lbStats:
之前介绍过 负载均衡指标管理器,有了它我们可以拿到所有服务器的状态changeListeners:
当allServerList
列表内容发生变化 会触发监听器serverStatusListeners:
服务的状态改变 会通过ServerStatusChangeListener
接口来通知 触发这一动作的主要是Pinger
来触发的
从上面的成员变量 我们知道BaseLoadBalancer
主要是聚合了 IPing
、IRule
两大组件。这两个组件也是最基础的组件选择服务器策略和验活。
initWithConfig
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
int pingIntervalTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerPingInterval,
Integer.parseInt("30")));
int maxTotalPingTime = Integer.parseInt(""
+ clientConfig.getProperty(
CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
Integer.parseInt("2")));
setPingInterval(pingIntervalTime);
setMaxTotalPingTime(maxTotalPingTime);
setRule(rule);
setPing(ping);
setLoadBalancerStats(stats);
rule.setLoadBalancer(this);
if (ping instanceof AbstractLoadBalancerPing) {
((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
}
logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
boolean enablePrimeConnections = clientConfig.get(
CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);
if (enablePrimeConnections) {
this.setEnablePrimingConnections(true);
PrimeConnections primeConnections = new PrimeConnections(
this.getName(), clientConfig);
this.setPrimeConnections(primeConnections);
}
init();
}
- 初始化最长Ping间隔时间
pingIntervalTime
和最大Ping时间maxTotalPingTime
没有地方使用到。 setPingInterval
启动Ping任务- 获取是否启用连接器验活标识
enablePrimeConnections
默认为false。如果该值为true 会在加载的时候对使用所有服务器进行检测,通过PrimeConnections
来设置服务器的readyToServe
状态
IPing
初始化的时候 会设置一个
public void setPingInterval(int pingIntervalSeconds) {
//ping间隔时间如果小于1s 那么直接返回
if (pingIntervalSeconds < 1) {
return;
}
this.pingIntervalSeconds = pingIntervalSeconds;
if (logger.isDebugEnabled()) {
logger.debug("LoadBalancer [{}]: pingIntervalSeconds set to {}",
name, this.pingIntervalSeconds);
}
//启动一个ping任务
setupPingTask();
}
进入setupPingTask()
void setupPingTask() {
//检查是否应该跳过Ping 如果 ping为null 或者ping为DummyPing将跳过
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
//强制快速执行一次ping的操作
forceQuickPing();
}
forceQuickPing()
强制快速执行一次ping操作,这个并不是Timer不快速,而是在主线程中执行一次ping操作 相当于初始化一次。
上面的代码主要是启动了一个timer 定时执行PingTask
.
PingTask
最终调用Pinger
的runPinger()
方法。代码如下:
public void runPinger() throws Exception {
if (!pingInProgress.compareAndSet(false, true)) {
return;
}
Server[] allServers = null;
boolean[] results = null;
Lock allLock = null;
Lock upLock = null;
/**下面这一坨代码主要作用是 通过pingerStrategy策略来 ping所有服务器的状态 把结果设置到upServerList中 顺便通过钩子方法回调关注了
* 服务状态变化的类。在操作upServerList 和获取allServerList都会加上锁。因为这两个字段不止这一个地方做了操作。
* */
try {
allLock = allServerLock.readLock();
allLock.lock();
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
results = pingerStrategy.pingServers(ping, allServers);
final List<Server> newUpList = new ArrayList<Server>();
final List<Server> changedServers = new ArrayList<Server>();
for (int i = 0; i < numCandidates; i++) {
boolean isAlive = results[i];
Server svr = allServers[i];
boolean oldIsAlive = svr.isAlive();
svr.setAlive(isAlive);
if (oldIsAlive != isAlive) {
changedServers.add(svr);
}
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
upServerList = newUpList;
upLock.unlock();
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress.set(false);
}
}
上述代码的逻辑很清晰:pingerStrategy.pingServers()
将每一个server的ping的结果都保存在 数组返回。
根据返回的每个服务器的ping的结果来找打服务状态变化的服务器们。然后通知监听器notifyServerStatusChangeListener
小Tips: 注意默认的ping的策略是 线性的Ping这对于具有很多服务器的系统不是一个很好的选择。所以如果Server过多可以选择并行来做Ping操作。
IRule
搞清楚了五大组件之一的IPing
如何工作之后 再看看 另一个组件IRule
在负载均衡中是如何工作的。
可以发现即使现在我们说到ILoadBalancer
我们还是在和五大组件打交道,可见其重要性。
BaseLoadBalancer
默认使用 RoundRobinRule
来完成服务筛选的工作。其实很简单,主要的工作都委托给了RoundRobinRule
,这里只是简单的调用RoundRobinRule
的choose()
方法。
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
addServers
该方法也是ILoadBalancer
留给子类实现的方法,用于将服务器列表添加到allServer
中,不限制是否唯一性,因此 可以通过添加重复的服务器到列表中来增加选中的概率。
public void addServers(List<Server> newServers) {
if (newServers != null && newServers.size() > 0) {
try {
ArrayList<Server> newList = new ArrayList<Server>();
newList.addAll(allServerList);
newList.addAll(newServers);
setServersList(newList);
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Exception while adding Servers", name, e);
}
}
}
最终调用setServersList()
来完成服务器的添加。
public void setServersList(List lsrv) {
Lock writeLock = allServerLock.writeLock();
logger.debug("LoadBalancer [{}]: clearing server list (SET op)", name);
ArrayList<Server> newServers = new ArrayList<Server>();
writeLock.lock();
try {
//下面将lsrv 转换成 list<Server> 这个方法兼容List<String>格式的服务列表
ArrayList<Server> allServers = new ArrayList<Server>();
for (Object server : lsrv) {
if (server == null) {
continue;
}
if (server instanceof String) {
server = new Server((String) server);
}
if (server instanceof Server) {
logger.debug("LoadBalancer [{}]: addServer [{}]", name, ((Server) server).getId());
allServers.add((Server) server);
} else {
throw new IllegalArgumentException(
"Type String or Server expected, instead found:"
+ server.getClass());
}
}
//如果和之前的服务列表不一致就触发ServerListChangeListener监听器
boolean listChanged = false;
if (!allServerList.equals(allServers)) {
listChanged = true;
if (changeListeners != null && changeListeners.size() > 0) {
List<Server> oldList = ImmutableList.copyOf(allServerList);
List<Server> newList = ImmutableList.copyOf(allServers);
for (ServerListChangeListener l: changeListeners) {
try {
l.serverListChanged(oldList, newList);
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error invoking server list change listener", name, e);
}
}
}
}
//如果开启连接检测 则使用PrimeConnections 来验活 并且设置服务器的ReadyToServe状态
//ReadyToServe状态默认是true
if (isEnablePrimingConnections()) {
for (Server server : allServers) {
if (!allServerList.contains(server)) {
server.setReadyToServe(false);
newServers.add((Server) server);
}
}
if (primeConnections != null) {
primeConnections.primeConnectionsAsync(newServers, this);
}
}
//赋值allServerList
allServerList = allServers;
//使用IPing来检测服务器是否可用 来设置服务器的alive状态
if (canSkipPing()) {
for (Server s : allServerList) {
s.setAlive(true);
}
upServerList = allServerList;
} else if (listChanged) {
forceQuickPing();
}
} finally {
writeLock.unlock();
}
}
上述的代码主要分为以下几个部分:
- 触发
ServerListChangeListener
事件 - 使用
PrimeConnections
来控制服务器的readyToServe
状态 默认为true 只有检测不通过的才会被设置成false - 赋值给属性
allServerList
- 使用
IPing
来检测服务器的alive
状态 默认为false 只有ping通过才会更改为false
关于readyToServe
和alive
在IRule
中会使用到。
demo
@Test
public void test1(){
List<Server> serverList = new ArrayList<>();
serverList.add(new Server("www.coredy1.com", 1));
serverList.add(new Server("www.coredy2.com", 2));
serverList.add(new Server("www.coredy3.com", 3));
BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
for (int i = 0; i < 6; i++) {
System.out.println(loadBalancer.choose(null));
}
}
结语
负载均衡器BaseLoadBalancer
就简单介绍到这。它主要是对五大组件的IPing
和IRule
的集成。但是我们发现它没有集成ServerList
,ServerListFilter
和ServerListUpdater
所以它不具备动态获取服务器的能力也不具备服务器过滤的能力更无法做到区域识别。