Ribbon

Spring Cloud客户端负载均衡: Ribbon


1. 客户端负载均衡vs服务端负载均衡

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具, 它基于Netflix Ribbon实现。

    1. 服务端负载均衡: 硬件LB、软件LB, 负载均衡设备或者软件模块维护一个可用的服务list, 通过心跳检测来剔除故障节点。
      服务端负载均衡
    1. 客户端负载均衡
      客户端负载均衡与服务端负载均衡最大的区别在于,客户端负载均衡中,所有的客户端节点都维护着自己想要访问的服务list,而这些list来自于服务注册中心。客户端与服务注册中心配合区完成服务端list的健康检测,spring cloud实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动化整合配置, 例如:
      1
      2
      3
      org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration //针对Eureka

      org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration //针对Consul

通过Spring Cloud Ribbon的封装, 在微服务中使用客户端负载均衡调用非常方便:

    1. 启动多个服务实例,并注册到注册中心。
    1. 服务的消费者(client)通过调用@LoadBalanced注解的RestTemplate来实现面向服务的接口调用。
      这样实现了服务提供者的高可用和服务消费者的负载均衡。

2. Ribbon提供的几种负载均衡策略

2.1 RoundRobinRule-轮询

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
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}

Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();

if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}

int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);

if (server == null) {
/* Transient. */
Thread.yield();
continue;
}

if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}

// Next.
server = null;
}

if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}

//顺序选择下一个
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}

2.2 ZoneAvoidanceRule-

2.3 RandomRule-随机

2.4 RetryRule-重试

2.5 WeightedResponseRule-

该策略继承自RoundRobinRule, 新增对各个实例的运行情况统计, 并根据计算的权重来选择实例。
维护一个每30秒执行一次的定时任务来计算权重:
实例X的权重区间 = total平均响应时间-X的平均响应时间 : 所以响应时间越短, 权重区间越大, 被选中的概率就越大。

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
class ServerWeight {

public void maintainWeights() {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return;
}

if (!serverWeightAssignmentInProgress.compareAndSet(false, true)) {
return;
}

try {
logger.info("Weight adjusting job started");
AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
LoadBalancerStats stats = nlb.getLoadBalancerStats();
if (stats == null) {
// no statistics, nothing to do
return;
}
double totalResponseTime = 0;
// find maximal 95% response time
for (Server server : nlb.getAllServers()) {
// this will automatically load the stats if not in cache
ServerStats ss = stats.getSingleServerStat(server);
totalResponseTime += ss.getResponseTimeAvg();
}
// weight for each server is (sum of responseTime of all servers - responseTime)
// so that the longer the response time, the less the weight and the less likely to be chosen
Double weightSoFar = 0.0;

// create new list and hot swap the reference
List<Double> finalWeights = new ArrayList<Double>();
for (Server server : nlb.getAllServers()) {
ServerStats ss = stats.getSingleServerStat(server);
double weight = totalResponseTime - ss.getResponseTimeAvg();
weightSoFar += weight;
finalWeights.add(weightSoFar);
}
setWeights(finalWeights);
} catch (Exception e) {
logger.error("Error calculating server weights", e);
} finally {
serverWeightAssignmentInProgress.set(false);
}

}
}

2.6 ClientConfigEnabledRoundRobinRule

一般并不直接使用,它内部包含了RoundRobinRule策略。但是通过继承它,实现一些高级策略,同时把RoundRobinRule作为备选。

2.6.1 BestAvailableRule

遍历负载均衡器维护的所有服务实例,过滤掉故障实例,找出其中并发请求数最小的(minimalConcurrentConnections)一个,所以该策略选出的是最空闲的实例。

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
public Server choose(Object key) {
//算法的核心依据是loadBalancerStats,当它为null时候,采用RoundRobinRule
if (loadBalancerStats == null) {
return super.choose(key);
}
List<Server> serverList = getLoadBalancer().getAllServers();
int minimalConcurrentConnections = Integer.MAX_VALUE;
long currentTime = System.currentTimeMillis();
Server chosen = null;
for (Server server: serverList) {
ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
//选择并发连接最少的实例
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}

2.6.2 PredicateBaseRule
  • 2.8.1 AvailablityFilteringRule
  • 2.8.2 ZoneAvoidanceRule