Spring Cloud Gateway網關整合

介紹

Spring Cloud Gateway爲Spring生態系統上的一個API網關組件,主要提供一種簡單而有效的方式路由映射到指定的API,併爲他們提供安全性、監控和限流等等。

創建項目

創建一個gmaya-gateway 項目。

修改pom文件

       <!--gateway網關,內置webflux 依賴-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-gateway</artifactId>
       </dependency>
       <!--eureka客戶端-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
       </dependency>
       <!--hystrix容錯降級-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
       </dependency>
       <!--健康監控-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
       
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

啓動類修改

@SpringBootApplication
@EnableDiscoveryClient

配置文件修改

server:
  port: 9200

spring:
  application:
    name: gmaya-geteway
  cloud:
    gateway:
      discovery:
        locator:
          # 開啓服務註冊和發現
          # 如果爲true,訪問路徑有兩個:
          # 1.ip:9200/gmaya-service-admin/user/test
          # 2.ip:9200/admin/user/test  (這個是Path自己定義的/admin/**)
          # 如果爲false,訪問路徑有一個ip:9200/admin/user/test
          enabled: false
          # 服務名配置爲小寫
          lower-case-service-id: true
      routes:
        # 系統服務
        - id: gmaya-service-admin # 不重複即可
          uri: lb://gmaya-service-admin # 需要轉發到的服務名稱
          predicates:
            # 以 /admin/開頭的路徑全部轉發到lb://gmaya-service-admin的服務上,此時gmaya-service-admin可以負載均衡
            - Path=/admin/**
          filters:
            # 去掉前面1個前綴,也就是真正轉發訪問的時候不帶/admin/
            - StripPrefix=1
# 註冊服務中心
eureka:
  instance:
    # 心跳時間,即服務續約間隔時間(缺省爲30s)
    lease-renewal-interval-in-seconds: 5
    # 發呆時間,即服務續約到期時間(缺省爲90s)
    lease-expiration-duration-in-seconds: 10
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka/
    healthcheck:
      enabled: true # 開啓健康檢查
    # 表示eureka client間隔多久去拉取服務註冊信息,默認爲30秒
    registry-fetch-interval-seconds: 5
# 監控
management:
  endpoints:
    web:
      exposure:
        # 通過HTTP公開所有的端點, 默認是info,health
        include: '*'
  endpoint:
    health:
      # 顯示完整信息,#默認是never(簡要信息)
      show-details: always


簡單測試

註冊中心:gmaya-service-center :8000
系統服務:gmaya-service-admin :9001
網關服務:gmaya-gateway :9200

啓動三個項目,訪問:

http://localhost:9200/admin/user/test

沒問題!

測一下gateway 的負載均衡,現在再把系統服務啓動一個9011端口。

-Dserver.port=9011

啓動之後查看效果

此時已經有了一個服務名一樣,但是端口不一樣的兩個服務了。
再次訪問接口。
查看後端打印情況,確實是輪詢方式實現了負載均衡。

添加hystrix熔斷降級

在分佈式系統中,網關作爲流量的入口,因此會有大量的請求進入網關,向其他服務發起調用,其他服務不可避免的會出現調用失敗(超時、異常),失敗時不能讓請求堆積在網關上,需要快速失敗並返回給客戶端,想要實現這個要求,就必須在網關上做熔斷、降級操作。

有兩種方式進行熔斷、降級

webflux方式

添加HystrixFallbackHandler

package top.gmaya.gmayagateway.handler;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;

/**
 * 熔斷、降級類
 * @author GMaya
 * @dateTime 2020/5/13 16:19
 */
@Component
@Slf4j
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse>
{
    @Override
    public Mono<ServerResponse> handle(ServerRequest serverRequest)
    {
        Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        originalUris.ifPresent(originalUri -> log.error("網關執行請求:{}失敗,hystrix服務降級處理", originalUri));
        // 可以根據自己的工具類返回統一格式
        Map<String,String> map = new HashMap();
        map.put("code","500");
        map.put("msg","服務出現異常,降級操作。");
        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(JSON.toJSONString(map)));
    }
}

添加GatewayRoutesConfig

package top.gmaya.gmayagateway.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import top.gmaya.gmayagateway.handler.HystrixFallbackHandler;

/**
 * 路由配置信息
 * @author GMaya
 * @dateTime 2020/5/13 16:19
 */
@Configuration
// 全參構造方法
@AllArgsConstructor
public class GatewayRoutesConfig {

    private final HystrixFallbackHandler hystrixFallbackHandler;

    @Bean
    public RouterFunction<?> routerFunction(){
        return RouterFunctions.route(RequestPredicates.path("/defaultFallback").and(RequestPredicates.accept(
                MediaType.TEXT_PLAIN)),hystrixFallbackHandler);
    }
}

修改配置文件

大部分都省略了,和上面的一樣。

spring:
  cloud:
    gateway:
      routes:
        # 系統服務
        - id: gmaya-service-admin # 不重複即可
          filters:
            # 去掉前面1個前綴,也就是真正轉發訪問的時候不帶/admin/
            - StripPrefix=1
            # 降級配置
            - name: Hystrix
              args:
                name: default
                fallbackUri: 'forward:/defaultFallback'
                
hystrix:
  command:
    default:  #default全局有效
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 3000 #斷路器超時時間,默認1000ms

測試

系統服務接口中添加延遲,模擬超時

此時訪問,三秒後返回設置好的熔斷信息

或者將系統服務直接關閉。


web請求方式

這種方式和正常controller中方法一樣

新建DefaultFallbackController

package top.gmaya.gmayagateway.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * web方式進行熔斷降級
 * @author GMaya
 * @dateTime 2020/5/13 17:05
 */
@RestController
@Slf4j
public class DefaultFallbackController {

    @GetMapping("defaultFallback")
    public Map<String,String> defaultFallback(){

        // 可以根據自己的工具類返回統一格式
        Map<String,String> map = new HashMap();
        map.put("code","500");
        map.put("msg","服務出現異常,降級操作。2");
        log.error("網關執行請求失敗,web方式記錄");
        return map;
    }

}

將第一種方式配置先去掉,重啓項目

測試

瀏覽器訪問


注意: 如果兩個都開啓了, 就會默認使用第一種方式。

添加限流

在高併發的系統中,往往需要在系統中做限流,一方面是爲了防止大量的請求使服務器過載,導致服務不可用,另一方面是爲了防止網絡攻擊。

常見的限流緯度有比如通過Ip來限流、通過uri來限流、通過用戶訪問頻次來限流。

採用springcloud gateway 爲我們提供了限流過濾器RequestRateLimiterGatewayFilterFactory。使用Redis和lua腳本實現了令牌桶的方式限流。

修改pom

引入

	   <!-- redis配置 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
       </dependency>
       <!--使用redis的lettuce連接池使用到,如果不用可不加-->
       <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-pool2</artifactId>
       </dependency>	


添加配置類

創建KeyResolverConfig類

package top.gmaya.gmayagateway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

/**
 * 路由限流配置
 * 配置文件中使用的那種限流 就是真正的限流方式,三種任選其一,不能同時存在
 * @author GMaya
 * @dateTime 2020/5/14 9:19
 */
@Configuration
public class KeyResolverConfig {

    /**
     * 根據ip限流
     * @return
     */
    @Bean(value = "hostAddrKeyResolver")
    public KeyResolver hostAddrKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
    /**
     * 根據路徑限流
     * @return
     */
   /* @Bean(value = "uriKeyResolver")
    public KeyResolver uriKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
    }*/
    /**
     * 根據用戶限流,參數中必須有user字段
     * @return
     */
    /*@Bean(value = "userKeyResolver")
    public KeyResolver userKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
    }*/
}

修改配置文件

spring:
  cloud:
    gateway:
      routes:
        # 系統服務
        - id: gmaya-service-admin # 不重複即可
          uri: lb://gmaya-service-admin # 需要轉發到的服務名稱
          predicates:
            # 以 /admin/開頭的路徑全部轉發到lb://gmaya-service-admin的服務上,此時gmaya-service-admin可以負載均衡
            - Path=/admin/**
          filters:
            # 去掉前面1個前綴,也就是真正轉發訪問的時候不帶/admin/
            - StripPrefix=1
            # 降級配置
            - name: Hystrix
              args:
                name: default
                fallbackUri: 'forward:/defaultFallback'
            # 限流配置
            - name: RequestRateLimiter
              args:
                # 用於限流的鍵的解析器的 Bean 對象的名字
                key-resolver: '#{@hostAddrKeyResolver}'
                # 令牌桶每秒填充平均速率(實際情況適當加大即可:10)
                redis-rate-limiter.replenishRate: 1
                # 令牌桶總容量(實際情況適當加大即可:20)
                redis-rate-limiter.burstCapacity: 3

測試

使用測試工具測試,每秒兩次訪問。

查看redis中的值

將令牌桶總容量中的3個值消耗完畢,再多次訪問即頁面就是一片空白。


TODO : 後續有時間,研究一下自定義限流返回信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章