自定義Gateway負載均衡(根據請求參數路由到不同服務器)


一 概述

   想讓post請求中相同參數走特定的服務器(例如age=25只能走服務a).post\get請求走自定義策略,get\delete請求走輪訓策略.

 

二 自定義負載均衡實例

   主要步驟:

      a 自定義全局過濾器CacheBodyGlobalFilter,把body中的數據緩存起來,此過濾器優先級較高,負責自定義負載均衡策略時拿不到post請求中body的數據

      b 路由規則接口 ICustomRule

      c 路由規則實現類CustomChooseRule

      d 自定義負載均衡CustomLoadBalancerFilter此優先級要比CacheBodyGlobalFilter低,負責先經過它,body數據沒緩存

      e 解析body的工具類 FilterRequestResponseUtil

   以People中的age內容路由(對psot put請求相同age只能通過相同服務,get\delete請求走自定義輪訓策略和框架一樣)

  1 cloud-parent  cloud-providea  cloud-provideb的創建

          

      (1)父工程pom文件(可能包含無用依賴) \及服務A B的創建

        

<?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.dp</groupId>
  <artifactId>cloud-parent</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>


    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.1.6.RELEASE</spring-boot.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
        <nacos.version>0.2.2.RELEASE</nacos.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
     <dependency>
       <groupId>cn.miludeer</groupId>
       <artifactId>jsoncode</artifactId>
       <version>1.2.4</version>
    </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
         <dependency>
              <groupId>com.baomidou</groupId>
               <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.3.1.tmp</version>
        </dependency>
        <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus</artifactId>
                <version>3.3.1.tmp</version>
        </dependency>
         <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.20</version>
 </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <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>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${nacos.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <modules>
        <module>cloud-providea</module>
    <module>cloud-getway</module>
    <module>cloud-provideb</module>
    </modules>
</project>
View Code

      (2)服務A服務B目錄結構(共用實體類沒提出來,都單獨創建了,懶得再創建common服務)

         以創建服務A為列(服務B和A一樣,只不過端口不一樣)

        

       Controller層代碼如下:

          

import org.cloud.provide.entity.People;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {

    
    @PostMapping
    public String   helloPost(@RequestBody People people) {                
        System.out.println("a=="+people.getName());
        return  "服務A  hello";
                
    }
    
    
    @PutMapping
    @RequestMapping(method=RequestMethod.PUT,value = "{age}")
    public String helloPut(@PathVariable int age) {        
        return "服務A  put"+age;
    }
    
    @GetMapping
    public String  helloGet(String age) {        
        return "服務A get"+age;
    }
}

        實體類Peopler如下:

        

@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
    
    
    private String name;
    private Integer age;
}

      yml配置文件(數據庫連接用不到沒刪除掉)

      

server:
  port: 7200 #服務B是7300
spring:
  application:
    name: nacos-provide
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848


  datasource:
    username: root
    password: 123
    url: jdbc:mysql://localhost:3306/cloud
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

mybatis-plus: 
    configuration : 
      map-underscore-to-camel-case : true
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mapper-locations: classpath:mapper/*.xml

    服務B同上,只不過yml配置文件端口是7300

  

        2 網關服務cloud-gateway的創建

      (1) pom yml文件如下

   pom.xml     

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.dp</groupId>
    <artifactId>cloud-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <groupId>com.dp</groupId>
  <artifactId>cloud-getway</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>cloud-getway</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-webflux</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

  </dependencies>
  
  
  <build>
      <plugins>
       <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>   
      
      </plugins>
  </build>
</project>
View Code

 

   yml

    

spring:
  application:
    name: getway
  cloud: #配置SpringCloudGateway的路由
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true # 服務名小寫
      routes:
        - id: nacos-provide  #路由的ID,沒有固定規則但求唯一,建議配合服務名
          uri: lb://nacos-provide # 匹配后提供服務的路由地址,lb代表從注冊中心獲取服務,且以負載均衡方式轉發
          predicates:
            - Path=/nacos-provide/** #斷言,路徑相匹配的進行路由
    nacos:
       discovery:
        server-addr: localhost:8848
  datasource:
    username: root
    password: 123
    url: jdbc:mysql://localhost:3306/cloud
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus: 
    configuration : 
      map-underscore-to-camel-case : true
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mapper-locations: classpath:mapper/*.xml 
    
    
    
server:
  port: 9000 #原來是http的8080
View Code

 

     

 (2)路由規則接口ICustomRule

   

/*
 * 路由規則
 */
public interface ICustomRule {

    
    ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient);

}

      (3)路由規則實現類

      

import java.net.URI;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.web.server.ServerWebExchange;
import com.alibaba.fastjson.JSONObject;
import reactor.core.publisher.Flux;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

public class CustomChooseRule    implements ICustomRule {
    
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    @Override
    public ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient) {

          URI originalUrl = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
          String instancesId = originalUrl.getHost();                     
          Flux<DataBuffer> dataBufferFlux = exchange.getRequest().getBody();
          //獲取body中的數據
          String body = FilterRequestResponseUtil.resolveBodyFromRequest(dataBufferFlux);
          //所有服務數據
          List<ServiceInstance> instances = discoveryClient.getInstances(instancesId);
          int index=0;
          
          //攔截nacos-provide服務也可攔截服務中特定url
          if(instancesId.equals("nacos-provide")){
             /*
              * 根據age中的數據訪問特定的服務,例如age=25 只能訪問服務a
              */
              if("POST"==exchange.getRequest().getMethodValue()) {
                  JSONObject json = JSONObject.parseObject(new String(body));
                  index =json.getString("age").hashCode() % instances.size();
              }else if("PUT"==exchange.getRequest().getMethodValue()) {
                  String str=originalUrl.toString();
                  String json=str.substring(str.lastIndexOf("/")+1, str.length());
                  index=json.hashCode()% instances.size();
              }else {
                  /*
                   * GET Delete請求采用輪訓算法
                   */
                   index = this.getAndIncrement() % instances.size();
              }

         }else {
           /*
            * 別的服務采用輪訓
            */
             index = this.getAndIncrement() % instances.size();
         }

        return instances.get(index);
    }


    
    /**
     * 計算得到當前調用次數
     * @return
     */
    public final int getAndIncrement(){
        int current;
        int next;

        do {
            current = atomicInteger.get();
            next = current >= Integer.MAX_VALUE ? 0 : current+1;
        }while (!atomicInteger.compareAndSet(current,next));   

        return next;
    }
}

     (3)CacheBodyGlobalFilter自定義全局過濾器把body中的數據緩存起來,負責拿不動body中的數據

      order設置的是Ordered.HIGHEST_PRECEDENCE,即最高優先級的過濾器。優先級設置這么高的原因是某些系統內置的過濾器可能也會去讀body,這樣就會導致我們自定義過濾器中獲取body的時候報body只能讀取一次這樣的錯誤

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @Description 把body中的數據緩存起來
 */
@Slf4j
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {


  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    if (exchange.getRequest().getHeaders().getContentType() == null) {
      return chain.filter(exchange);
    } else {
      return DataBufferUtils.join(exchange.getRequest().getBody())
          .flatMap(dataBuffer -> {
            DataBufferUtils.retain(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux
                .defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                exchange.getRequest()) {
              @Override
              public Flux<DataBuffer> getBody() {
                return cachedFlux;
              }
            };
            //exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, cachedFlux);

            return chain.filter(exchange.mutate().request(mutatedRequest).build());
          });
    }
  }

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }

}

 

      (4)自定義負責均衡策略

      

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.springframework.beans.factory.config.BeanPostProcessor;
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.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR;
/**
 * @Description 自定義負載均衡
 **/
public class CustomLoadBalancerFilter extends LoadBalancerClientFilter   {

    private final DiscoveryClient discoveryClient;

    private final List<ICustomRule> chooseRules;

    public CustomLoadBalancerFilter(LoadBalancerClient loadBalancer,
                                          LoadBalancerProperties properties,
                                          DiscoveryClient discoveryClient) {
        super(loadBalancer, properties);
        this.discoveryClient = discoveryClient;
        this.chooseRules = new ArrayList<>();
        chooseRules.add(new CustomChooseRule());
    }

    protected ServiceInstance choose(ServerWebExchange exchange) {
        if(!CollectionUtils.isEmpty(chooseRules)){
            Iterator<ICustomRule> iChooseRuleIterator = chooseRules.iterator();
            while (iChooseRuleIterator.hasNext()){
                ICustomRule chooseRule = iChooseRuleIterator.next();
                ServiceInstance choose = chooseRule.choose(exchange,discoveryClient);
                if(choose != null){
                    return choose;
                }
            }
        }
        return loadBalancer.choose(
                 ((ServiceInstance) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
    }
    
    /*
     * 降低優先級/要比CacheBodyGlobalFilter優先級低,要不然緩存不了body數據
     */
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
      }
    
}

      (5)解析body數據的工具類

      

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import reactor.core.publisher.Flux;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class FilterRequestResponseUtil {

  /**
   * spring cloud gateway 獲取post請求的body體
   * @param body
   * @return
   */
  public static String resolveBodyFromRequest( Flux<DataBuffer> body){
    AtomicReference<String> bodyRef = new AtomicReference<>();
    // 緩存讀取的request body信息
    body.subscribe(dataBuffer -> {
      CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
      DataBufferUtils.release(dataBuffer);
      bodyRef.set(charBuffer.toString());
    });
    //獲取request body
    return bodyRef.get();

  }

  /**
   * 讀取body內容
   * @param body
   * @return
   */
  public static String resolveBodyFromRequest2( Flux<DataBuffer> body){
    StringBuilder sb = new StringBuilder();

    body.subscribe(buffer -> {
      byte[] bytes = new byte[buffer.readableByteCount()];
      buffer.read(bytes);
      DataBufferUtils.release(buffer);
      String bodyString = new String(bytes, StandardCharsets.UTF_8);
      sb.append(bodyString);
    });
    return formatStr(sb.toString());
  }

  /**
   * 去掉空格,換行和制表符
   * @param str
   * @return
   */
  private static String formatStr(String str){
    if (str != null && str.length() > 0) {
      Pattern p = Pattern.compile("\\s*|\t|\r|\n");
      Matcher m = p.matcher(str);
      return m.replaceAll("");
    }
    return str;
  }
}

    (6)把自定義類啟動時加載到spring容器

    

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.GlobalFilter;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;

import reactor.core.publisher.Mono;

@Configuration
public class GetawayConfig {
    
        @Bean
        public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
                                                                 LoadBalancerProperties properties,
                                                                DiscoveryClient discoveryClient) {
                return new CustomLoadBalancerFilter(client, properties,discoveryClient);
        }

}

 

     yml文件:

      

spring:
  application:
    name: getway
  cloud: #配置SpringCloudGateway的路由
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true # 服務名小寫
      routes:
        - id: nacos-provide  #路由的ID,沒有固定規則但求唯一,建議配合服務名
          uri: lb://nacos-provide # 匹配后提供服務的路由地址,lb代表從注冊中心獲取服務,且以負載均衡方式轉發
          predicates:
            - Path=/nacos-provide/** #斷言,路徑相匹配的進行路由
    nacos:
       discovery:
        server-addr: localhost:8848
  datasource:
    username: root
    password: 123
    url: jdbc:mysql://localhost:3306/cloud
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus: 
    configuration : 
      map-underscore-to-camel-case : true
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mapper-locations: classpath:mapper/*.xml 
    
    
    
server:
  port: 9000 #原來是http的8080
View Code

    

3 簡單測試 

  (1)post請求如下(age相同請求多少次都是走服務A put請求同post請求):

          

        (2)  get請求測試如下(走輪訓策略,每次請求服務不一樣)

      1第一次請求

        

      2 第二次請求

        

 

 

代碼路徑:  https://github.com/kangchangchang/gateway.git

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM