Skip to content

Nacos负载均衡配置

967字约3分钟

springbootspringcloud

2023-12-06

引入Loadbalancer

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

引入loadbalance后SpringCloud会使用ip轮训策略调用nacos中的服务,我们可以通过修改配置来定义负载均衡策略。我们也可以使用Ribbon来实现,目前ribbon已不再维护,不推荐。

负载均衡策略

1 Ribbon载均衡策略

Ribbon有多种负载均衡策略

  • 随机 RandomRule
  • 轮询 RoundRobinRule
  • 重试 RetryRule
  • 最低并发 BestAvailableRule
  • 可用过滤 AvailabilityFilteringRule
  • 响应时间加权重 ResponseTimeWeightedRule
  • 区域权重 ZoneAvoidanceRule

2. LoadBalancer载均衡策略

LoadBalancer貌似只提供了两种负载均衡器,不指定的时候默认用的是轮询

  • RandomLoadBalancer 随机
  • RoundRobinLoadBalancer 轮询
  • NacosLoadBalancer nacos实现的负载均衡, 同集群优先,同ip段优先

配置负载均衡策略

我们可以在springboot的@Configuration类上配置策略, 如下配置随机策略,同时优化http请求工具,采用RestTemplate实现。

@Configuration
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
public class GatewayConfig {
    ....
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

自定义负载均衡策略

典型场景:我们需要服务在负载时优先在相同集群服务,然后在进行随机。该场景我们推荐使用NacosLoadBalancer ,以下示例只是展示自定义方法,如有更个性化的策略,可以参考实现。不要忘记在config类上指定策略!!

**
 *  自定义实现loadbanlance, 同一集群优先调用
 *
 * @author: PengHao
 * @date: 2023/9/22 10:30:11
 * @version: v1.0
 */
@Slf4j
public class NacosSameClusterWeightedRule implements ReactorServiceInstanceLoadBalancer {

    // 注入当前服务的nacos的配置信息
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    // loadbalancer 提供的访问的服务列表
    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public NacosSameClusterWeightedRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    /**
     * 服务器调用负载均衡时调的放啊
     * 此处代码内容与 RandomLoadBalancer 一致
     */
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(supplier, serviceInstances);
        });
    }

    /**
     * 对负载均衡的服务进行筛选的方法
     * 此处代码内容与 RandomLoadBalancer 一致
     */
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
        }

        return serviceInstanceResponse;
    }

    /**
     * 对负载均衡的服务进行筛选的方法
     * 自定义
     * 此处的 instances 实例列表  只会提供健康的实例  所以不需要担心如果实例无法访问的情况
     */
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }
        // 获取当前服务所在的集群名称
        String currentClusterName = nacosDiscoveryProperties.getClusterName();
        log.debug("==> get current cluster name: {}.", currentClusterName);
        // 过滤在同一集群下注册的服务 根据集群名称筛选的集合
        List<ServiceInstance> sameClusterNameInstList  = instances.stream().filter(i-> StringUtils.equals(i.getMetadata().get("nacos.cluster"),currentClusterName)).collect(Collectors.toList());
        ServiceInstance sameClusterNameInst;
        log.debug("==> get same cluster install size : {}.", sameClusterNameInstList.size());
        if (sameClusterNameInstList.isEmpty()) {
            // 如果为空,则根据权重直接过滤所有服务列表
            sameClusterNameInst = getHostByRandomWeight(instances);
        } else {
            // 如果不为空,则根据权重直接过滤所在集群下的服务列表
            sameClusterNameInst = getHostByRandomWeight(sameClusterNameInstList);
        }
        log.debug("==> get same cluster install host: {}.", sameClusterNameInst.getHost());
        return new DefaultResponse(sameClusterNameInst);
    }

    private ServiceInstance getHostByRandomWeight(List<ServiceInstance> sameClusterNameInstList){

        List<Instance> list = new ArrayList<>();
        Map<String,ServiceInstance> dataMap = new HashMap<>();
        // 此处将 ServiceInstance 转化为 Instance 是为了接下来调用nacos中的权重算法,由于入参不同,所以需要转换,此处建议打断电进行参数调试,以下是我目前为止所用到的参数,转化为map是为了最终方便获取取值到的服务对象
        sameClusterNameInstList.forEach(i->{
            Instance ins = new Instance();
            Map<String, String> metadata = i.getMetadata();

            ins.setInstanceId(metadata.get("nacos.instanceId"));
            ins.setWeight(new BigDecimal(metadata.get("nacos.weight")).doubleValue());
            ins.setClusterName(metadata.get("nacos.cluster"));
            ins.setEphemeral(Boolean.parseBoolean(metadata.get("nacos.ephemeral")));
            ins.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy")));
            ins.setPort(i.getPort());
            ins.setIp(i.getHost());
            ins.setServiceName(i.getServiceId());

            ins.setMetadata(metadata);

            list.add(ins);
            // key为服务ID,值为服务对象
            dataMap.put(metadata.get("nacos.instanceId"),i);
        });
        // 调用nacos官方提供的负载均衡权重算法
        Instance hostByRandomWeightCopy = ExtendBalancer.getHostByRandomWeightCopy(list);

        // 根据最终ID获取需要返回的实例对象
        return dataMap.get(hostByRandomWeightCopy.getInstanceId());
    }
}

class ExtendBalancer extends Balancer {
    /**
     * 根据权重选择随机选择一个
     */
    public static Instance getHostByRandomWeightCopy(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}

陕ICP备2021014644号-1