Load Balance负载均衡是用于解决一台机器(一个进程)无法解决所有请求而产生的一种算法。
像nginx可以使用负载均衡分配流量,ribbon为客户端提供负载均衡,dubbo服务调用里的负载均衡等等,很多地方都使用到了负载均衡。
使用负载均衡带来的好处很明显:
负载均衡有好几种实现策略,常见的有:
我们以ribbon的实现为基础,看看其中的一些算法是如何实现的。
ribbon是一个为客户端提供负载均衡功能的服务,它内部提供了一个叫做ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、获取所有的服务器列表、获取可用的服务器列表等等。
还提供了一个叫做IRule的接口代表负载均衡策略:
1
2
3
4
5
|
public
interface
IRule{
public
Server choose(Object key);
public
void
setLoadBalancer(ILoadBalancer lb);
public
ILoadBalancer getLoadBalancer();
}
|
IRule接口的实现类有以下几种:
其中RandomRule表示随机策略、RoundRobin表示轮询策略、WeightedResponseTimeRule表示加权策略、BestAvailableRule表示请求数最少策略等等。
随机策略很简单,就是从服务器中随机选择一个服务器,RandomRule的实现代码如下:
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
|
public
Server choose(ILoadBalancer lb, Object key) {
if
(lb ==
null
) {
return
null
;
}
Server server =
null
;
while
(server ==
null
) {
if
(Thread.interrupted()) {
return
null
;
}
List
List
int
serverCount = allList.size();
if
(serverCount ==
0
) {
return
null
;
}
int
index = rand.nextInt(serverCount);
// 使用jdk内部的Random类随机获取索引值index
server = upList.get(index);
// 得到服务器实例
if
(server ==
null
) {
Thread.yield();
continue
;
}
if
(server.isAlive()) {
return
(server);
}
server =
null
;
Thread.yield();
}
return
server;
}
|
RoundRobin轮询策略表示每次都取下一个服务器,比如一共有5台服务器,第1次取第1台,第2次取第2台,第3次取第3台,以此类推:
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
|
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
) {
// retry 10 次
List
List
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);
// incrementAndGetModulo方法内部使用nextServerCyclicCounter这个AtomicInteger属性原子递增对serverCount取模得到索引值
server = allServers.get(nextServerIndex);
// 得到服务器实例
if
(server ==
null
) {
Thread.yield();
continue
;
}
if
(server.isAlive() && (server.isReadyToServe())) {
return
(server);
}
server =
null
;
}
if
(count >=
10
) {
log.warn(
"No available alive servers after 10 tries from load balancer: "
+ lb);
}
return
server;
}
|
BestAvailableRule策略用来选取最少并发量请求的服务器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
Server choose(Object key) {
if
(loadBalancerStats ==
null
) {
return
super
.choose(key);
}
List
// 获取所有的服务器列表
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) {
// 比较各个服务器之间的请求数,然后选取请求数最少的服务器并放到chosen变量中
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
if
(chosen ==
null
) {
// 如果没有选上,调用父类ClientConfigEnabledRoundRobinRule的choose方法,也就是使用RoundRobinRule轮询的方式进行负载均衡
return
super
.choose(key);
}
else
{
return
chosen;
}
}
|
ServerList中提供了3个instance,分别是:
1
2
3
|
compute-service:
2222
compute-service:
2223
compute-service:
2224
|
然后使用不同的IRule策略查看负载均衡的实现。
首先先使用ribbon提供的LoadBalanced注解加在RestTemplate上面,这个注解会自动构造LoadBalancerClient接口的实现类并注册到Spring容器中。
1
2
3
4
5
|
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return
new
RestTemplate();
}
|
接下来使用RestTemplate进行rest操作的时候,会自动使用负载均衡策略,它内部会在RestTemplate中加入LoadBalancerInterceptor这个拦截器,这个拦截器的作用就是使用负载均衡。
例子中,我们的实例的name叫做compute-service,里面提供了一个方法add用于相加2个Integer类型的数值。
loadbalance的具体操作:
1
2
3
4
5
6
7
8
|
public
String loadbalance() {
ServiceInstance serviceInstance = loadBalancerClient.choose(
"compute-service"
);
StringBuilder sb =
new
StringBuilder();
sb.append(
"host: "
).append(serviceInstance.getHost()).append(
", "
);
sb.append(
"port: "
).append(serviceInstance.getPort()).append(
", "
);
sb.append(
"uri: "
).append(serviceInstance.getUri());
return
sb.toString();
}
|
RandomRule:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Configuration
public
class
RibbonConfiguration {
@Autowired
private
SpringClientFactory springClientFactory;
@Bean
public
IRule ribbonRule() {
return
new
RandomRule();
}
}
|
测试结果如下,确实是随机获取的:
1
2
3
4
5
6
7
8
9
10
|
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2224
, uri: http:
//192.168.31.113:2224
host:
192.168
.
31.113
, port:
2224
, uri: http:
//192.168.31.113:2224
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
|
RoundRobinRule:
1
2
3
4
|
@Bean
public
IRule ribbonRule() {
return
new
RandomRule();
}
|
测试结果如下,确实是轮询每个服务器的:
1
2
3
4
5
6
7
8
9
10
11
12
|
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2224
, uri: http:
//192.168.31.113:2224
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2224
, uri: http:
//192.168.31.113:2224
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2224
, uri: http:
//192.168.31.113:2224
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2222
, uri: http:
//192.168.31.113:2222
host:
192.168
.
31.113
, port:
2224
, uri: http:
//192.168.31.113:2224
|
BestAvailableRule:
1
2
3
4
|
@Bean
public
IRule ribbonRule() {
return
new
BestAvailableRule();
}
|
如果直接访问浏览器的话,测试结果如下(因为每次访问完请求数都变成0,下次遍历永远都是2223这个端口的实例):
1
2
3
4
5
6
7
|
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
host:
192.168
.
31.113
, port:
2223
, uri: http:
//192.168.31.113:2223
..
|
使用wrk模拟并发请求,结果会出现多个实例:
1
|
wrk -c
1000
-t
10
-d 10s http:
//localhost:3333/test
|