【你好Ribbon】十三:Ribbon负载均衡器接口ILoadBalancer-最基础的负载均衡器BaseLoadBalancer

目录
前言
ILoadBalancer接口
核心方法
继承关系
BaseLoadBalancer
重要的成员变量
initWithConfig
IPing
IRule
addServers
demo
结语
前言
前面我们对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();
1
2
3
4
5
addServers: 添加一组服务器
chooseServer: 根据key从负载均衡中获取一个服务器
markServerDown: 标记一个服务器挂了
getAllServers: 获取所有的服务器,包括可访问的不可访问的
getReachableServers: 获取所有可用的服务器
继承关系


AbstractLoadBalancer: 最主要就是新增了一个获取负载均衡指标管理的方法 LoadBalancerStats getLoadBalancerStats() 意为着子类可以根据服务的状态来获取健康的服务器
NoOpLoadBalancer: 从名字可以看出 没有任何操作的 所有的方法都返回的是null
BaseLoadBalancer: 最基础的负载均衡器,注意不是一个抽象类,意为着你可以直接拿过来使用。但是服务列表服务动态化,无法做到服务过滤,无法做到区域识别
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>();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
初始化最长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(); 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
进入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();
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
上述代码的逻辑很清晰: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;
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
         }
     }
 }
1
2
3
4
5
6
7
8
9
10
11
12
最终调用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();
   }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
上述的代码主要分为以下几个部分:

触发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));
    }
  }
1
2
3
4
5
6
7
8
9
10
11
结语
负载均衡器BaseLoadBalancer 就简单介绍到这。它主要是对五大组件的IPing和IRule的集成。但是我们发现它没有集成ServerList,ServerListFilter和ServerListUpdater 所以它不具备动态获取服务器的能力也不具备服务器过滤的能力更无法做到区域识别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值