spring cloud gateway 自定义负载均衡getLoadBalancer()获取的服务总是同一个服务

根据业务需求,我需要自定义负载均衡规则,获取相应的服务,然后返回;

 代码如下:
     继承LoadBalancerClientFilter 重写choose方法;
 protected ServiceInstance choose(ServerWebExchange exchange) {
       //获取用户票据信息
       String ticket= AuthTicketUtils.getBxAuthTicket(exchange.getRequest());
       logger.info("=====获取用户登录票据信息:{}============",ticket);
       if(!StringUtils.isEmpty(ticket)){
           //根据ticket获取用户信息
           if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
               RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
               String userId=getUserNo(ticket);
               logger.info("========当前操作用户信息为:{}=============",userId);
               if(null == userId){
                   return super.choose(exchange);
               }
               //获取服务名称
               String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
               logger.info("===========当前选择服务名称为:{}===========",serviceId);
               //这里使用服务ID 和 用户ID 做为选择服务实例的key
               GrayscaleProperties grayscaleProperties=new GrayscaleProperties();
               grayscaleProperties.setServerId(serviceId);
               grayscaleProperties.setUserId(userId);
               return client.choose(serviceId,grayscaleProperties);
           }
       }
        return super.choose(exchange);
    }
当调用client.choose(String name,Object hint)方法时,将会调用我的规则,我的自定义规则代码如下:首先继承了AbstractLoadBalancerRule类,重写choose方法
 public Server choose(Object o) {
        logger.info("=======服务选择:{}=======",o);
        //获取当前请求的所有服务
        logger.info("============LoadBalancer:{}==============",this.getLoadBalancer().toString());
        List<Server> servers = this.getLoadBalancer().getReachableServers();
                 //TODO  逻辑代码省略
                 return null;
                }

当调用this.getLoadBalancer()时,返回的总是上一次正确的服务,举例来说我有A、B、C三个服务,当第一次请求A服务的时候正常返回,第二次B服务请求的时候,this.getLoadBalancer()返回的对象信息是A服务的信息;

附图:

图片说明

图片说明

不要用

this.getLoadBalancer().getReachableServers()

这个,
你可以注入一个

DiscoveryClient discoveryClient;

List<String> ids=discoveryClient.getServices()
获得到所有的 service ID
List<ServiceInstance> service = discoveryClient.getInstances(String serviceId)
就是获取到里面的服务,同名服务可能有多个。

我就是这样来做服务选择的

我的灰度服务代码

package com.learn.gateway.client;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author zhang
 * @projectName spring-cloud-parent
 * @title GrayLoadBalancerClientFilter
 * @package com.learn.gateway.client
 * @description 灰度负载均衡
 * @date 2019/11/18 7:08 下午
 */
@Slf4j
@Component
public class GrayLoadBalancerClientFilter extends LoadBalancerClientFilter {

    private final DiscoveryClient discoveryClient;

    private static final String VERSION_KEY = "myself.eureka.version";

    public GrayLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties, DiscoveryClient discoveryClient) {
        super(loadBalancer, properties);
        this.discoveryClient = discoveryClient;
    }

    /**
     * 选择服务
     * @param exchange
     * @return
     */
    @Override
    protected ServiceInstance choose(ServerWebExchange exchange) {
        log.info("服务分发操作");

        // 获得请求头
        HttpHeaders headers = exchange.getRequest().getHeaders();

        // 获取请求同的token
        String token = headers.getFirst("token");


        // 获取用户携带的版本号
            String version = headers.getFirst("version");

        if (StringUtils.isEmpty(version)) {
            return super.choose(exchange);
        }

        log.debug("从请求头中获取token:{},\t从请求头中获取到的 version 版本号:{}",token,version);

        // 获取用户获取的项目serviceId
        URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);

        assert uri != null;
        String instancesId = uri.getHost();

        log.debug("请求的服务的instancesId:{}",instancesId);

        // 获取服务的信息
        List<ServiceInstance> instances = discoveryClient.getInstances(instancesId);
        log.debug("从eureka中获取的服务instances为:{}", instances);

        // 通过version查找到服务
        if (instances != null && instances.size()>0) {
            List<ServiceInstance> versionSer = instances.stream().filter(i -> {
                assert version != null;
                return version.equals(i.getMetadata().get(VERSION_KEY));
            }).collect(Collectors.toList());

            // 没有找到对应版本号的服务,
            if (versionSer.size() == 0) {
                return super.choose(exchange);
            }

            // 找到对应版本好的服务。。。
            return  versionSer.get(0);
        }
        return super.choose(exchange);
    }
}

这个问题我也遇到过,不知道你后面是怎么解决的,我当时是继承例RandomRule,重写例choose,之后,我将这个类设置成例多例的(即多加一个@Scope(value="prototype")),就正常了。你可以试试

qq107581 qq107581 11月前

这个问题我也遇到过,不知道你后面是怎么解决的,我当时是继承例RandomRule,重写例choose,之后,我将这个类设置成例多例的(即多加一个@Scope(value="prototype")),就正常了。你可以试试
这个回答就很棒!!!

https://blog.csdn.net/cyxinda/article/details/98884205

楼主,我也遇到了同样问题,折腾了好几天了,你最后是以什么方式解决的?