Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
- 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
- 服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
例:
至此简单完成了负载均衡,默认是轮询。还可以使用nacos的权重进行负载均衡。
如何切换Ribbon中赋值均衡的规则,而不是使用默认的轮询方式?
只需在客户端(consume)工程中的SpringBoot配置类ConfigBean配置一个返回具体方法的bean即可,如下:
@Bean
public IRule MyRule(){
/* RandomRule为Ribbon中自带规则实现之一,随机规则 */
return new RandomRule();
}
自定义Ribbon负载均衡算法
如果觉得Ribbon中自带的负载均衡规则无法满足你的需求,那也可以自定义Ribbon负载均衡算法。
需要注意的地方:
自定义的Ribbon算法类不能放在主启动类所在的包及子包下(确切来说是不能放在@ComponentScan注解的包及子包下),否则会被全局应用到Ribbon服务中。 应该把自定义算法类放在另外新建的包下,且这个类应该是为【配置类】即加上注解@Configuration。
public Server choose(Object o) {
try {
// 拿到配置文件中的集群名称 BJ
String clusterName = nacosDiscoveryProperties.getClusterName();
// 自定义的元数据
String targetVersion = nacosDiscoveryProperties.getMetadata().get("target-version");
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务名称
String name = loadBalancer.getName();
// 拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1.找到指定服务的所有实例 A
List<Instance> instances = namingService.selectInstances(name, true);
Collection<Instance> sameMetaDataInstances = instances;
// 使用相同元数据的实例
if (targetVersion != null) {
sameMetaDataInstances = instances.stream().filter(instance -> ObjectUtils.equals(targetVersion, instance.getMetadata().get("target-version"))).collect(Collectors.toList());
if (CollectionUtils.isEmpty(sameMetaDataInstances)) {
log.error("未找到元数据匹配的目标实例,请检查配置。target-version={},instances={}", targetVersion, instances);
return null;
}
}
// 2.过滤出相同集群下的所有实例 B
List<Instance> sameClusterInstances = sameMetaDataInstances.stream().filter(instance -> Objects.equals(instance.getClusterName(), clusterName)).collect(Collectors.toList());
// 3.如果B为空,就使用A
List<Instance> instancesToBeChosen;
if (CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToBeChosen = instances;
log.warn("发生跨集群的调用,name={},clusterName={},instances={}", name, clusterName, instances);
} else {
instancesToBeChosen = sameClusterInstances;
}
// 4.基于权重的负载平衡算法,返回1个实例
Instance ins = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
log.info("选择的实例是 port={},instance={}", ins.getPort(), ins);
return new NacosServer(ins);
} catch (NacosException e) {
log.error("发生异常", e);
return null;
}
}