gateway自定义负载均衡策略

项目由于某些架构原因,要求相同的客户的请求要路由到固定同一个实例里面,主要技术使用的是spring cloud alibaba 2021.1 cloud版本 Hoxton.SR9;

一开始我记得nacos的负载均衡使用的是ribbon,找了半天的ribbon的负载均衡依赖,发现在2021.1版本里面nacos已经把ribbon删除了,那我只能用默认实现load balancer了;

可以看到我们依赖了

spring-cloud-starter-loadbalancer

这个就是spring cloud官方提供的负载均衡依赖;

pom版本

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.crq.demo</groupId>
  <artifactId>demo-gateway</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <java.version>11</java.version>
    <file_encoding>UTF-8</file_encoding>
    <swagger.version>2.9.2</swagger.version>
    <knife4j.version>3.0.2</knife4j.version>
    <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
    <micrometer.version>1.8.0</micrometer.version>

  </properties>

  <dependencies>
    <!--  spring gateway 网关-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--lettuce 依赖commons-pool-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
    </dependency>
    <!-- actuator -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>


    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      <version>3.0.2</version>
    </dependency>

    <!--热部署-->

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
    </dependency>


    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.10</version>
    </dependency>

    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.2</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
      <scope>provided</scope>
    </dependency>

    <!--jasypt加密-->
    <dependency>
      <groupId>com.github.ulisesbocchio</groupId>
      <artifactId>jasypt-spring-boot-starter</artifactId>
      <version>2.1.0</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <dependency>
      <groupId>com.github.xiaoymin</groupId>
      <artifactId>knife4j-spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.restdocs</groupId>
      <artifactId>spring-restdocs-mockmvc</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>io.micrometer</groupId>
      <artifactId>micrometer-registry-prometheus</artifactId>

    </dependency>

    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
    </dependency>

  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>


      <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>${swagger.version}</version>
        <optional>true</optional>
      </dependency>
      <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-dependencies</artifactId>
        <version>${knife4j.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!--<dependency>-->
        <!--<groupId>io.micrometer</groupId>-->
        <!--<artifactId>micrometer-registry-prometheus</artifactId>-->
        <!--<version>${micrometer.version}</version>-->
      <!--</dependency>-->

      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>${spring-cloud-alibaba.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.3.7.RELEASE</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.6.1</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <encoding>UTF-8</encoding>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.20.1</version>
          <configuration>
            <skipTests>true</skipTests>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>

    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>application.yml</include>
          <include>application-${profiles.active}.yml</include>
          <include>bootstrap.yml</include>
          <include>bootstrap-${profiles.active}.yml</include>
          <include>**/*.xml</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
    <finalName>demo-gateway</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 指定该Main Class为全局的唯一入口 -->
          <mainClass>com.crq.demo.gateway.GatewayApplication</mainClass>
          <layout>ZIP</layout>
        </configuration>
        <executions>
          <execution>
            <goals>
              <!--可以把依赖的包都打包到生成的Jar包中-->
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

springboot 版本和 cloud 版本; springcloud alibaba 这些依赖的版本要统一对应起来,不然启动报错会抓狂;

配置文件

因为使用到了nacos的配置中心,所以我们需要引入依赖

<!-- 解决bootstrap 不生效-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency> 

在boot版本2.5.8.RELEASE里面如果没有引入 这个依赖 bootstrap开头的配置文件不会起作用,一个大坑,目前很多更新资料不全,只能靠前辈们趟坑了;我们用的是2.3.7没有这个问题;如果使用boot 2.5.x版本的可以注意一下;

bootstrap.yaml

spring:
  application:
    name: demo-gateway-app
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: nacos的地址+端口
        namespace: namespace
      config:
        username: nacos
        password: nacos
        server-addr: nacos的地址+端口
        namespace: namespace

application.yml

spring:
  profiles:
    active: ${profiles.active}

---
spring:
  profiles: dev
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: service-1
          uri: lb://service-a
          predicates:
             - Path=/serviceA/**
          filters:
            - StripPrefix= 1

启动类增加注解

@SpringBootApplication
@EnableDiscoveryClient
@Slf4j
@EnableOpenApi
@LoadBalancerClient(name = "service-a", configuration = MyLoadBalancerConfig.class)

@LoadBalancerClient 注解 这个是我们自定义的负载均衡实现类,需要我们创建一个MyLoadBalancerConfig.java,这个类注入了两个实例分别是

ReactiveLoadBalancerClientFilter实例
ReactorServiceInstanceLoadBalancer实例
@Configuration
public class MyLoadBalancerConfig {
 @Bean
    @SuppressWarnings("deprecation")
    public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
            ObjectProvider<ServiceInstanceListSupplier> serviceInstanceSupplier) {
        return new DemoLoadBalancer(serviceInstanceSupplier);
    }

    @Bean
    public ReactiveLoadBalancerClientFilter reactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
            LoadBalancerProperties properties) {
        return new NewReactiveLoadBalancerClientFilter(clientFactory, properties);
    }

}

ReactiveLoadBalancerClientFilter源码

/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.filter;

import java.net.URI;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;

/**
 * A {@link GlobalFilter} implementation that routes requests using reactive Spring Cloud
 * LoadBalancer.
 *
 * @author Spencer Gibb
 * @author Tim Ysewyn
 * @author Olga Maciaszek-Sharma
 */
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;
	}

	@Override
	public int getOrder() {
		return LOAD_BALANCER_CLIENT_FILTER_ORDER;
	}

	@Override
	@SuppressWarnings("Duplicates")
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
		if (url == null
				|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		// preserve the original url
		addOriginalRequestUrl(exchange, url);

		if (log.isTraceEnabled()) {
			log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName()
					+ " url before: " + url);
		}

		return choose(exchange).doOnNext(response -> {

			if (!response.hasServer()) {
				throw NotFoundException.create(properties.isUse404(),
						"Unable to find instance for " + url.getHost());
			}

			ServiceInstance retrievedInstance = response.getServer();

			URI uri = exchange.getRequest().getURI();

			// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
			// if the loadbalancer doesn't provide one.
			String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
			if (schemePrefix != null) {
				overrideScheme = url.getScheme();
			}

			DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(
					retrievedInstance, overrideScheme);

			URI requestUrl = reconstructURI(serviceInstance, uri);

			if (log.isTraceEnabled()) {
				log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
			}
			exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		}).then(chain.filter(exchange));
	}

	protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
		return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
	}

	@SuppressWarnings("deprecation")
     //这个方法就是从服务实例中选择服务的实例;所以我们只需要重写这个方法就可以了,直接创建一个新的类集成本类,重写choose方法
	private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
		URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory
				.getInstance(uri.getHost(), ReactorServiceInstanceLoadBalancer.class);
		if (loadBalancer == null) {
			throw new NotFoundException("No loadbalancer available for " + uri.getHost());
		}
    //这里需要注意我们需要 自己构建一个request;把我们需要的参数传递给loadbalancer ,一般是客户的id之类的,这个request,一般要包含exchange,把exchange作为信息载体
		return loadBalancer.choose(createRequest());
	}

	@SuppressWarnings("deprecation")
	private Request createRequest() {
		return ReactiveLoadBalancer.REQUEST;
	}

}

上面的choose中使用的是

ReactorLoadBalancer来委派真实服务实例,所以我们也需要一个自己的ReactorLoadBalancer实现,也就是上面的DemoLoadBalancer

自定义的request

@SuppressWarnings("deprecation")
public class Demoequest extends org.springframework.cloud.client.loadbalancer.reactive.DefaultRequest {

    public Demoequest (ServerWebExchange context) {
        super(context);
    }

}

@Slf4j
public class DemoLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    // 服务列表
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;



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

 @Override
    @SuppressWarnings("deprecation")
    public Mono<Response<ServiceInstance>> choose(Request request) {

        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
      //request 强转为自己的request,
        DemoRequest demoRequest = (DemoRequest ) request;
        //根据参数获取
        ServerWebExchange context = (ServerWebExchange) demoRequest.getContext();
        String userId = context.getRequest().getQueryParams().get("userId").get(0);
        //获取userId的 hash值
        int hash = userId.hashCode();
         if(hash>=0){
          return   supplier.get().next().map(i->getInstanceResponse(i,hash));
        }
        
        
        //没有自定义负载均衡的将进行随机规则访问
        return supplier.get().next().map(this::getInstanceResponse);
    }


    //这里就可以计算这个用户的真实的服务实例,hash值然后取余;基本实现了功能
    private  Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, int hash) {
        int index = hash % instances.size();
        return new DefaultResponse(instances.get(index));
    }

    /**
     * 使用随机数获取服务
     * @param instances
     * @return
     */
    @SuppressWarnings("deprecation")
    private Response<ServiceInstance> getInstanceResponse(
            List<ServiceInstance> instances) {

        if (instances.isEmpty()) {
            return new EmptyResponse();
        }
        // 随机算法
        int size = instances.size();
        Random random = new Random();
        ServiceInstance instance = instances.get(random.nextInt(size));

        return new DefaultResponse(instance);
    }

}

                
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Cloud Gateway支持多种负载均衡策略,例如随机、轮询、权重等。如果现有的负载均衡策略不能满足你的需求,你可以自定义负载均衡策略。 首先,你需要实现`org.springframework.cloud.client.loadbalancer.reactive.LoadBalancer`接口来定义你的负载均衡策略。然后,你需要创建一个`org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction`实例,用于将服务的URI重写为负载均衡的服务实例地址。最后,你需要将这个自定义负载均衡策略应用到Spring Cloud Gateway的路由规则中。 以下是一个示例,展示了如何定义一个基于特定请求头的自定义负载均衡策略: ```java public class CustomLoadBalancer implements LoadBalancer<ServiceInstance> { private final String headerName; public CustomLoadBalancer(String headerName) { this.headerName = headerName; } @Override public Mono<Response<ServiceInstance>> choose(Request request) { Object headerValue = request.headers().getFirst(headerName); String serviceName = "my-service"; // 根据请求头的值选择服务实例 ServiceInstance serviceInstance = ...; return Mono.just(new DefaultResponse(serviceInstance)); } } public class CustomLoadBalancerGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomLoadBalancerGatewayFilterFactory.Config> { public CustomLoadBalancerGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { LoadBalancer<ServiceInstance> loadBalancer = new CustomLoadBalancer(config.getHeaderName()); RewriteFunction<String, String> rewriteFunction = uri -> { // 将URI重写为负载均衡的服务实例地址 ServiceInstance serviceInstance = loadBalancer.choose(Request.create("", new HttpHeaders())).block().getServer(); return "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + uri; }; return new RewritePathGatewayFilterFactory().apply(new RewritePathGatewayFilterFactory.Config().setRewriteFunction(rewriteFunction)); } public static class Config { private String headerName; public String getHeaderName() { return headerName; } public void setHeaderName(String headerName) { this.headerName = headerName; } } } ``` 在上面的示例中,`CustomLoadBalancer`实现了`LoadBalancer`接口,并基于特定的请求头选择服务实例。`CustomLoadBalancerGatewayFilterFactory`则将`CustomLoadBalancer`应用到Spring Cloud Gateway的路由规则中,并将服务的URI重写为负载均衡的服务实例地址。最后,你可以在路由规则中使用`CustomLoadBalancerGatewayFilterFactory`来定义自定义负载均衡策略。 ```yaml spring: cloud: gateway: routes: - id: my-route uri: http://my-service filters: - CustomLoadBalancer=my-header ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值