springcloud gateway如何實現路由和負載均衡

簡介:

gateway主要是做路由 負載,過濾 主要是替代zuul 1.x 性能比zuul好 zuul是基於

Servlet ,gateway是基於spring-webflux 用的netty+reactor

yml文件

實現路由 負載 的配置 親自測試

spring:
  application:
    name: xgyx_gateway
  cloud:
    discovery:
      locator:
        enabled: true
    gateway:
       routes:
       - id: a  #隨便定義不重復就好
         uri: lb://xgyx-welfareservice-x  #服務名稱
         predicates:
         - Path=/m/**  #前端訪問需加入例如 http:ip:port/m
         filters:
         - StripPrefix=1 #訪問後端服務過濾掉m 必填否則找不到後端服務也可以在服務加上統一路徑
         - name: Hystrix  #熔斷
           args:
             name: default
             fallbackUri: forward:/defaultfallback  #熔斷後訪問路徑
       - id: b
         uri: lb://xgyx-welfareservice
         predicates:
         - Path=/welfare/**
         filters:
         - StripPrefix=1
         - name: Hystrix
           args:
             name: default
             fallbackUri: forward:/fallback
#熔斷時間
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy:  SEMAPHORE
          thread:
            timeoutInMilliseconds: 300000  #熔斷時間

上面是用瞭兩天時間根據官網上的demo和說明自己測的可以使用 上面 stripPrefix 用的是 PrefixPath 過濾器 其他過濾器使用可以看官網

springcloud gateway 自定義負載均衡

相關類及接口

LoadbalancerClientFilter:使用ribbon負載均衡,默認使用該類(已不推薦使用)

/** @deprecated */
@Deprecated
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
    public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;
    private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
    protected final LoadBalancerClient loadBalancer;
    private LoadBalancerProperties properties;
 
    public LoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        this.loadBalancer = loadBalancer;
        this.properties = properties;
    }
 
    public int getOrder() {
        return 10100;
    }
 
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            if (log.isTraceEnabled()) {
                log.trace("LoadBalancerClientFilter url before: " + url);
            }
 
            ServiceInstance instance = this.choose(exchange);
            if (instance == null) {
                throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());
            } else {
                URI uri = exchange.getRequest().getURI();
                String overrideScheme = instance.isSecure() ? "https" : "http";
                if (schemePrefix != null) {
                    overrideScheme = url.getScheme();
                }
 
                URI requestUrl = this.loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
                if (log.isTraceEnabled()) {
                    log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                }
 
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                return chain.filter(exchange);
            }
        } else {
            return chain.filter(exchange);
        }
    }
 
    protected ServiceInstance choose(ServerWebExchange exchange) {
        return this.loadBalancer.choose(((URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR)).getHost());
    }
}

說明:默認使用該類,可通過下述方法使用ReactiveLoadbalancerClientFilter

​”You already have RibbonLoadBalancerClient on your classpath. It will be used by default.
As Spring Cloud Ribbon is in maintenance mode. We recommend switching to ” + BlockingLoadBalancerClient.class.getSimpleName() + ” instead.
In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false`
or remove spring-cloud-starter-netflix-ribbon from your project.”

ReactiveLoadBalancerClientFilter:負載均衡攔截器

public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
    private static final Log log = LogFactory.getLog(ReactiveLoadBalancerClientFilter.class);
    private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
    private final LoadBalancerClientFactory clientFactory;
    private LoadBalancerProperties properties; 
    public ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        this.clientFactory = clientFactory;
        this.properties = properties;
    }
 
    public int getOrder() {
        return 10150;
    }
 
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
                            //url不為null且協議為lb,或者url以lb開頭
 
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            if (log.isTraceEnabled()) {
                log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
            }
 
            return this.choose(exchange).doOnNext((response) -> {
                            //獲取ServiceInstance實例,進行一些處理
 
                if (!response.hasServer()) {
                            //如果沒有serviceInstance,直接拋出異常
 
                    throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());
                } else {    //如果有serviceInstance,進行相關處理
                    URI uri = exchange.getRequest().getURI();
                    String overrideScheme = null;
                    if (schemePrefix != null) {
                        overrideScheme = url.getScheme();
                    }
 
                    DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance((ServiceInstance)response.getServer(), overrideScheme);
                    URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
                    if (log.isTraceEnabled()) {
                        log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                    }
 
                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                }
            }).then(chain.filter(exchange));
        } else {
            return chain.filter(exchange); //如果獲取不到serviceInstance,直接進行後續過濾
        }
    }
 
    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        ReactorLoadBalancer<ServiceInstance> loadBalancer = (ReactorLoadBalancer)this.clientFactory.getInstance(uri.getHost(), ReactorLoadBalancer.class, new Class[]{ServiceInstance.class});
        if (loadBalancer == null) {
            throw new NotFoundException("No loadbalancer available for " + uri.getHost());
        } else {
            return loadBalancer.choose(this.createRequest());
        }
    }//選擇服務實例
 
    private Request createRequest() {
        return ReactiveLoadBalancer.REQUEST;
    }
}

ReactorLoadBalancer:負載均衡接口

public interface ReactorLoadBalancer<T> extends ReactiveLoadBalancer<T> {
    Mono<Response<T>> choose(Request request); 
    default Mono<Response<T>> choose() {
        return this.choose(REQUEST);
    }
} 
*********************** 
public interface ReactorServiceInstanceLoadBalancer extends ReactorLoadBalancer<ServiceInstance> {
}

RoundRobinLoadbalancer:負載均衡使用輪詢

public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
    private final AtomicInteger position; 
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceId;
 
************
構造方法
 
    public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
    public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) {
 
************
普通方法
 
    public Mono<Response<ServiceInstance>> choose(Request request) {
        if (this.serviceInstanceListSupplierProvider != null) {
            ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
            return ((Flux)supplier.get()).next().map(this::getInstanceResponse);
        } else {
            ServiceInstanceSupplier supplier = (ServiceInstanceSupplier)this.serviceInstanceSupplier.getIfAvailable(NoopServiceInstanceSupplier::new);
            return ((Flux)supplier.get()).collectList().map(this::getInstanceResponse);
        }
    }
 
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        } else {
            int pos = Math.abs(this.position.incrementAndGet());
            ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());
            return new DefaultResponse(instance);
        }
    }//使用輪詢獲取實例
}

示例:

參數id為偶數時,輸出hello new version

網關

配置文件

spring:
  application:
    name: hello-gateway
  cloud:
    consul:
      host: 172.18.0.20
      port: 8500
    loadbalancer:
      ribbon:
        enabled: false
    gateway:
      routes:
        - id: myRoute
          uri: lb://hello-service
          predicates:
            - Path=/hello

自定義過濾器

@Component
public class CustomLoadBalancerClientFilter implements GlobalFilter, Ordered {
    private static final Log log = LogFactory.getLog(org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.class);
 
    @Resource
    private final LoadBalancerClientFactory clientFactory;
 
    @Resource
    private LoadBalancerProperties properties;
 
    public CustomLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        this.clientFactory = clientFactory;
        this.properties = properties;
    }
 
    public int getOrder() {
        return 10149;
    }
 
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            if (log.isTraceEnabled()) {
                log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
            }
 
            return this.choose(exchange).doOnNext((response) -> {
                if (!response.hasServer()) {
                    throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());
                } else {
                    URI uri = exchange.getRequest().getURI();
                    String overrideScheme = null;
                    if (schemePrefix != null) {
                        overrideScheme = url.getScheme();
                    }
 
                    int id=Integer.parseInt(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("id")));
                    if (id%2==0){
                        while (!"new".equals(response.getServer().getMetadata().get("version"))){
                            try {
                                response=this.choose(exchange).toFuture().get();
                            }catch (Exception e){
                                System.out.println(e.getMessage());
                            }
                        }
                    }
 
                    DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(), overrideScheme);
 
                    System.out.println(exchange.getRequest().getQueryParams().getFirst("id")+"對應server的version為:"+serviceInstance.getMetadata().get("version"));
                    URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
                    if (log.isTraceEnabled()) {
                        log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                    }
 
                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                }
            }).then(chain.filter(exchange));
        } else {
            return chain.filter(exchange);
        }
    }
 
    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        assert uri != null;
        ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(uri.getHost(), ReactorLoadBalancer.class, new Class[]{ServiceInstance.class});
        if (loadBalancer == null) {
            throw new NotFoundException("No loadbalancer available for " + uri.getHost());
        } else {
            return loadBalancer.choose(this.createRequest());
        }
    }
 
    private Request createRequest() {
        return ReactiveLoadBalancer.REQUEST;
    }
}

同名應用hello-service1

配置文件

spring:
  application:
    name: hello-service
  cloud:
    consul:
      host: 172.18.0.20
      port: 8500
      discovery:
        instance-id: ${spring.application.name}-${random.int}
        tags: version=old

controller 層

@RestController
public class HelloController { 
    @RequestMapping("/hello")
    public String hello(){
        return "hello old version";
    }
}

同名應用hello-service2

配置文件

spring:
  application:
    name: hello-service
  cloud:
    consul:
      host: 172.18.0.20
      port: 8500
      discovery:
        instance-id: ${spring.application.name}-${random.int}
        tags: version=new

controller 層

@RestController
public class HelloController { 
    @RequestMapping("/hello")
    public String hello(){
        return "hello new version";
    }
}

測試輸出

consul註冊的應用

參數測試

當id為偶數時,輸出為hello new version

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: