004-负载均衡-Ribbon
# 一、负载均衡
# 1.1 服务端负载均衡
# 1.2 客户端负载均衡
# 二、使用Ribbon实现负载均衡
# 2.1 添加依赖
nacos 中已经包含 ribbon 依赖
# 2.2 Ribbon 组成
接口 | 作用 | 默认值 |
---|---|---|
IClientConfig | 读取配置 | DefaultClientConfigImpl |
IRule | 负载均衡规则,选择不同的实例 | ZoneAvoidanceRule |
IPing | 筛选掉ping不通的实例 | DummyPing (什么都不做的Ping) |
ServerList<Server> | 交给Ribbon的实例列表 | Ribbon:ConfigurationBasedServerList (Ribbon自己把配置写在这)Spring Cloud Alibaba:NacosServerList (利用Naocs Server Client获取实例列表) |
ServerListFilter<Server> | 过滤掉不符合条件的实例 | ZonePreferenceServerListFilter (Zone代表一个机房不同机架,这个值代表优先获取相同机架的列表) |
ILoadBalancer | Ribbon的入口 | ZoneAwareLoadBalancer |
ServerListUpdater | 更新交给Ribbon的List的策略 | PollingServerListUpdater (让Ribbon定时更新ServerList) |
# 2.3 Ribbon 内置的复杂均衡规则
规则名称 | 特点 |
---|---|
AvailabilityFilteringRule | 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一-个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态 |
BestAvailableRule | 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过 |
RandomRule | 随机选择一个Server |
已废弃,作用同WeightedResponseTimeRule,根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低 | |
RetryRule | 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | 轮询选择,轮询index, 选择index对应位置的Server |
WeightedResponseTimeRule | 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低 |
ZoneAvoidanceRule | 复合判断Server所Zone的性能和Server的可用性选择Server,在没有Zone的环境下,类似于轮询(RoundRobinRule ) |
# 2.4 Ribbon 细粒度自定义配置
假设内容中心同时调用两个微服务(用户中心、订单中心)Ribbon可以实现让 内容中心 调用 用户中心 服务的时候使用
随机策略
,调用 订单中心 服务的时候用默认的策略
(没有Zone的情况下类似轮询),下面围绕这个场景来看看Ribbon如何配置。
# 2.4.1 代码配置
先在项目下新建一个包ribbonconfiguration
,与SpringBoot启动类所在包平级,这里由于父子上下文重叠问题 (opens new window),必须这样做:
如果父子上下文重叠,那么细粒度配置就变成了全局配置(异常,非正常情况)
在ribbonconfiguration
包中新建 RibbonConfiguration.java
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
// 选择Ribbon随机负载均衡策略
return new RandomRule();
}
}
2
3
4
5
6
7
8
再在项目中新建UserCenterRibbonConfiguration类 为 user-center (用户中心)指定Ribbon自定义配置类
@Configuration
@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
2
3
4
# 2.4.2 配置属性方式
注释掉上面的配置,添加以下配置,可以得到相同效果。
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2
3
# 2.4.3 配置总结
- 尽量使用属性配置方式
- 在同一个微服务内尽量保持单一性
# 2.5 Ribbon 全局配置
将上面的配置改为以下方式即可:
@Configuration
//@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)//全局配置
public class UserCenterRibbonConfiguration {
}
2
3
4
5
# 2.6 Ribbon 自定义支持
- Java 方式
在RibbonConfiguration
添加对应的Bean,就可以将Ping规则修改为PingUrl。
@Bean
public IPing ping(){
return new PingUrl();
}
2
3
4
-
<clientName>.ribbon.NFLoadBalancerClassName
:ILoadBalancer
实现类<clientName>.ribbon.NFLoadBalancerRuleClassName
:IRule
实现类<clientName>.ribbon.NFLoadBalancerPingClassName
:IPing
实现类<clientName>.ribbon.NIWSServerListClassName
:ServerList
实现类<clientName>.ribbon.NIWSServerListFilterClassName
:ServerListFilter
实现类
# 2.7 Ribbon 饥饿加载
通过开启饥饿加载加速初次请求Ribbon的速度。
ribbon:
eager-load:
enabled: true
clients: client1, client2, client3
2
3
4
# 2.8 扩展Ribbon——支持Nacos权重
Nacos支持权重配置,将性能较差的机器权重设低,性能较好的机器权重设高,让请求更多的请求到性能高的机器上。
Ribbon本身的负载均衡策略是不支持Nacos权重的。
要想实现该功能,先实现AbstractLoadBalancerRule
自定义负载均衡算法
/**
* 支持Nacos权重的负载均衡算法
*/
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//读取配置文件,并初始化NacosWeightedRule
}
@Override
public Server choose(Object key) {
try {
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要获取微服务的名称。
String name = loadBalancer.getName();
//拿到服务发现的相关Api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//nacos client自动通过基于权重的负载均衡算法返回一个实例。
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("选择的实例是:port:{} instance:{}", instance.getPort(), instance);
return new NacosServer(instance);
} catch (NacosException e) {
return null;
}
}
}
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
🤯:配置方式参考 [Ribbon 细粒度自定义配置](#2.4 Ribbon 细粒度自定义配置)
# 2.9 扩展Ribbon——支持Nacos集群配置
虽然Spring Cloud Alibaba支持集群配置,例如:
spring:
cloud:
nacos:
discovery:
# 北京机房集群
cluster-name: BJ
2
3
4
5
6
但在调用时,服务消费者并不会优先调用同集群的实例。
本节来探讨如何扩展Ribbon,从而实现同集群优先调用的效果,并且还能支持Nacos权重配置。
要想实现该功能,先实现AbstractLoadBalancerRule
自定义负载均衡算法
/**
* 扩展Ribbon-同一集群优先调用,并支持权重配置
*/
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
try {
// 拿到配置文件中的集群名称 BJ
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务的名称
String name = loadBalancer.getName();
// 拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1. 找到指定服务的所有实例 A
final List<Instance> instances = namingService.selectInstances(name, true);
// 2. 过滤出相同集群下的所有实例 B
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 3. 如果B是空,就用A
List<Instance> instancesToBeChosen = new ArrayList<>();
if (CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToBeChosen = instances;
log.warn("发生跨集群的调用, name = {}, clusterName = {}, instances = {}", name, clusterName, instances);
} else {
instancesToBeChosen = sameClusterInstances;
}
// 4. 基于权重的负载均衡算法,返回1个实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("发生异常了", e);
return null;
}
}
}
/**
* 负载均衡算法
*/
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
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
🤯:配置方式参考 [Ribbon 细粒度自定义配置](#2.4 Ribbon 细粒度自定义配置)
# 2.10 扩展Ribbon——基于元数据的版本控制
同一个微服务在线上可能多个版本共存,比如**:用户服务的v1 只能 调用订单服务的v1**
spring:
cloud:
nacos:
metadata:
# 自己这个实例的版本
version: v1
# 允许调用的提供者版本
target-version: v1
2
3
4
5
6
7
8
🤔 优先选择同集群下,符合metadata的实例,如果同集群加没有符合metadata的实例,就选择所有集群下,符合metadata的实例
@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
// 负载均衡规则:优先选择同集群下,符合metadata的实例
// 如果没有,就选择所有集群下,符合metadata的实例
// 1. 查询所有实例 A
// 2. 筛选元数据匹配的实例 B
// 3. 筛选出同cluster下元数据匹配的实例 C
// 4. 如果C为空,就用B
// 5. 随机选择实例
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
// 所有实例
List<Instance> instances = namingService.selectInstances(name, true);
List<Instance> metadataMatchInstances = instances;
// 如果配置了版本映射,那么只调用元数据匹配的实例
if (StringUtils.isNotBlank(targetVersion)) {
metadataMatchInstances = instances.stream()
.filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(metadataMatchInstances)) {
log.warn("未找到元数据匹配的目标实例!请检查配置。targetVersion = {}, instance = {}", targetVersion, instances);
return null;
}
}
List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
// 如果配置了集群名称,需筛选同集群下元数据匹配的实例
if (StringUtils.isNotBlank(clusterName)) {
clusterMetadataMatchInstances = metadataMatchInstances.stream()
.filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
clusterMetadataMatchInstances = metadataMatchInstances;
log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
}
}
Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
return new NacosServer(instance);
} catch (Exception e) {
log.warn("发生异常", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
ExtendBalancer extends Balancer {
/**
* 根据权重,随机选择实例
*
* @param instances 实例列表
* @return 选择的实例
*/
public static Instance getHostByRandomWeight2(List<Instance> instances) {
return getHostByRandomWeight(instances);
}
}
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
69
70
71
72
73
74
75
🤯:配置方式参考 [Ribbon 细粒度自定义配置](#2.4 Ribbon 细粒度自定义配置)