第03章-Ribbon负载均衡
# 3.1 负载均衡介绍
负载均衡的两种方式
- 服务端负载均衡
- 客户端负载均衡
# 3.1.1 服务端负载均衡
客户端请求服务端的nginx,nginx通过负载均衡算法决定将请求转发到哪台server
# 3.1.2 客户端负载均衡
在调用端获取server列表,根据负载均衡算法决定去请求哪一台server
# 3.2 Ribbon介绍
# 3.2.1 Ribbon简介
Ribbon是Netflix开源的客户端侧负载均衡器。
引入Ribbon后架构将演进成如下所示:
# 3.2.2 Ribbon组成
接口 | 作用 | 默认值 |
---|---|---|
IClientConfig | 读取配置 | DefaultclientconfigImpl |
IRu1e | 负载均衡规则,选择实例 | ZoneAvoidanceRule |
IPing | 筛选掉ping不通的实例 | Dummyping |
ServerList | 交给Ribbon的实例列表 | Ribbon:ConfigurationBasedServerList Spring cloud Alibaba:NacosServerList |
ServerListFilter | 过滤掉不符合条件的实例 | ZonePreferenceServerListFilter |
ILoadBalancer | Ribbon的入口 | ZoneAwareLoadBalancer |
ServerListUpdater | 更新交给Ribbon的List的策略 | PollingserverListupdater |
# 3.2.3 Ribbon负载均衡规则
Ribbon内置有如下负载均衡规则:
规则名称 | 特点 |
---|---|
AvailabilityFiteringRule | 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态 |
BestAvailableRule | 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过 |
RandomRule | 随机选择一个Server |
ResponseTimeWeightedRule | 已废弃,作用同WeightedResponseTimeRule |
RetryRule | 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | 轮询选择,轮询index,选择index对应位置的Server |
WeightedResponseTimeRule | 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低 |
ZoneAvoidanceRule(默认) | 复合判断Server所Zone的性能和Server的可用性选择Server,在没有Zone的环境下,类似于轮询(RoundRobinRule) |
# 3.3 Nacos整合Ribbon
添加依赖
spring-cloud-starter-alibaba-nacos-discovery 子依赖中有Ribbon,不需要再手动添加依赖。
写注解
@Bean @LoadBalanced //负载均衡注解 public RestTemplate restTemplate() { RestTemplate template = new RestTemplate(); return template; }
1
2
3
4
5
6使用服务名访问
private final RestTemplate restTemplate; public ShareDTO findById(Integer id) { // 直接通过spring.application.name指定的服务名user-center访问服务 UserDTO userDTO = restTemplate.getForObject("http://user-center/users/{userId}", UserDTO.class, userId); // ······ }
1
2
3
4
5
6
7
# 3.4 配置Ribbon
# 3.4.1 负载均衡配置方式
方式一、Java代码方式配置
配置IRule
/** * Ribbon负载均衡规则,此配置类必须不能被Spring注解扫描到,因为存在父子上下文问题 * 如果此配置被Spring扫描到,则会变成全局配置,使用ribbon请求任意服务都会使用本规则 */ @Configuration public class RibbonConfig { @Bean public IRule ribbonRule() { return new RandomRule(); } }
1
2
3
4
5
6
7
8
9
10
11Java代码配置调用服务和访问规则
/** * 使用java代码的方式配置ribbon * 配置负载均衡的服务,和负载均衡策略 * 表示请求user-center服务时,会使用RibbonConfig指定的策略进行负载均衡 */ @Configuration @RibbonClient(name = "user-center", configuration = RibbonConfig.class) public class UserCenterRibbonConfig { }
1
2
3
4
5
6
7
8
9
方式二、属性配置方式
配置格式:<clientName>.ribbon.NFLoadBalancerRuleClassName
如下配置之后,和上述Java代码配置效果相同,请求user-center时,会使用NFLoadBalancerRuleClassName指定的负载均衡规则
# 属性配置Ribbon负载均衡规则
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
1
2
3
4
2
3
4
两种配置方式对比
配置方式 | 优点 | 缺点 |
---|---|---|
代码配置 | 基于代码,更加灵活 | 有小坑(父子上下文) 线上修改得重新打包、发布 |
属性配置 | 易上手 配置更加直观 线上修改无需重新打包、发布 优先级更高 | 极端场景下没有代码配置方式灵活 |
# 3.4.2 全局负载均衡配置
上一节配置方式是对指定的服务配置负载均衡规则,如果想对所有的服务都使用相同的负载均衡规则,可以使用全局负载均衡配置。有以下两种配置方式:
方式一:让CempenentScan上下文重叠(强烈不建议使用)方式二:Java编码方式
@Configuration @RibbonClients(defaultConfiguration = RibbonConfig.class) public class UserCenterRibbonConfig { }
1
2
3
4
# 3.4.3 Ribbon其他配置
配置格式:<clientName>.ribbon.<如下配置>
- NFLoadBalancerClassName:ILoadBalancer 实现类
- NFLoadBalancerRuleClassName:IRule实现类
- NFLoadBalancerPingclassName:IPing 实现类
- NIWSServerListclassName:ServerList实现类
- NIWSServerListFilterClassName:ServerListFilter实现类
# 3.4.4 饥饿加载
ribbon默认懒加载,第一次请求远程服务才会加载,导致第一次访问慢,可以开启饥饿加载:
ribbon:
eager-load:
enabled: true
clients: user-center,content-center
1
2
3
4
2
3
4
# 3.5 拓展:基于Nacos配置自定义负载均衡规则
# 3.5.1 自定义基于nacos权重的IRule
/**
* 自定义基于Nacos权重的ribbon IRule
* 自定义IRule可以实现IRule接口或继承AbstractLoadBalancerRule抽象类
* 权重可以在nacos server控制台设置
*/
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@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("选择实例是 {}:{}", instance.getIp(), instance.getPort());
return new NacosServer(instance);
} catch (NacosException e) {
log.error("获取服务实例出错");
return null;
}
}
}
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
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
参考:nacos权重负载均衡实现 (opens new window)
# 3.5.2 实现基于nacos权重,并且尽量调用同名集群下的服务
/**
* 自定义IRule,尽量调用同集群下的服务,并且基于权重
*/
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
try {
// 拿到配置文件中的集群名称 Shanghai
String clusterName = nacosDiscoveryProperties.getClusterName();
// 想要请求的微服务的名称
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
String name = loadBalancer.getName();
// 拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1. 找到指定服务的所有实例 A
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;
}
}
}
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
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
# 3.5.3 基于元数据匹配的IRule
参考:[扩展Ribbon支持基于元数据的版本管理](
编辑 (opens new window)
上次更新: 2023/06/04, 12:34:19