主頁 > 後端開發 > 新一代服務網關Gateway的實踐筆記

新一代服務網關Gateway的實踐筆記

2022-07-13 07:29:03 後端開發

3、SpringCloud Gateway

file

Spring Cloud Gateway 是Spring Cloud團隊的一個全新專案,基于Spring 5.0、SpringBoot2.0、Project Reactor 等技術開發的網關,旨在為微服務架構提供一種簡單有效統一的API路由管理方式,

Spring Cloud Gateway 作為SpringCloud生態系統中的網關,目標是替代Netflix Zuul,Gateway不僅提供統一路由方式,并且基于Filter鏈的方式提供網關的基本功能,例如:安全,監控/指標,和限流,

總結:微服務網關就是一個系統,通過暴露該微服務網關系統,方便我們進行相關的鑒權,安全控制,日志統一處理,易于監控,限流等相關功能,

實作微服務網關的技術有很多,

  • nginx:Nginx (engine x) 是一個高性能的HTTP和反向代理web服務器,同時也提供了IMAP/POP3/SMTP服務
  • zuul :Zuul 是 Netflflix 出品的一個基于 JVM 路由和服務端的負載均衡器,
  • spring-cloud-gateway:是spring 出品的基于spring的網關專案,集成斷路器,路徑重寫,性能比Zuul好,

我們使用gateway這個網關技術,無縫銜接到基于spring cloud的微服務開發中來,

gateway官網:

https://spring.io/projects/spring-cloud-gateway

3.1 Gateway作業原理

我們在學習Gateway之前,先弄清楚Gateway的作業原理,后面使用它的各個功能時,就知道該如何使用了,作業流程圖如下:

file

Gateway的執行流程如下:

1:Gateway的客戶端回向Spring Cloud Gateway發起請求,請求首先會被HttpWebHandlerAdapter進行提取組裝成網關的背景關系,然后網關的背景關系會傳遞到DispatcherHandler,

2:DispatcherHandler是所有請求的分發處理器,DispatcherHandler主要負責分發請求對應的處理器,比如將請求分發到對應RoutePredicateHandlerMapping(路由斷言處理器映射器),

3:路由斷言處理映射器主要用于路由的查找,以及找到路由后回傳對應的FilteringWebHandler,

4:FilteringWebHandler主要負責組裝Filter鏈表并呼叫Filter執行一系列Filter處理,然后把請求轉到后端對應的代理服務處理,處理完畢后,將Response回傳到Gateway客戶端,
在Filter鏈中,通過虛線分割Filter的原因是,過濾器可以在轉發請求之前處理或者接收到被代理服務的回傳結果之后處理,所有的Pre型別的Filter執行完畢之后,才會轉發請求到被代理的服務處理,被代理的服務把所有請求完畢之后,才會執行Post型別的過濾器,

3.2 Gateway路由

Gateway路由配置分為基于配置的靜態路由設定和基于代碼動態路由配置,

靜態路由是指在application.yml中把路由資訊配置好了,而動態路由則支持在代碼中動態加載路由資訊,更加靈活,我們接下來把這2種路由操作都實作一次,

3.2.1 業務說明

file

如上圖:

1:用戶所有請求以/order開始的請求,都路由到 hailtaxi-order服務
2:用戶所有請求以/driver開始的請求,都路由到 hailtaxi-driver服務
3:用戶所有請求以/pay開始的請求,都路由到 hailtaxi-pay服務

3.2.2 基于配置路由設定

file

如上圖所示,正是Gateway靜態路由配置:

1:用戶所有請求以/order開始的請求,都路由到 hailtaxi-order服務
2:用戶所有請求以/driver開始的請求,都路由到 hailtaxi-driver服務
3:用戶所有請求以/pay開始的請求,都路由到 hailtaxi-pay服務

配置引數說明:

routes:路由配置
- id:唯一識別符號
uri:路由地址,可以是 lb://IP:埠     也可以是   lb://${spring.application.name}
predicates:斷言,是指路由條件
- Path=/driver/**:路由條件,Predicate 接受一個輸入引數,回傳一個布林值結果,這里表示匹配所有以driver開始的請求,
filters:過濾器
- StripPrefix=1:真實路由的時候,去掉第1個路徑,路徑個數以/分割區分

測驗url:http://localhost:8001/driver/info/1

3.2.3 基于代碼路由配置

我們同樣實作上面的功能,但這里基于代碼方式實作,所有路由規則我們可以從資料庫中讀取并加載到程式中,基于代碼的路由配置我們只需要創建RouteLocator并添加路由配置即可,代碼如下:

/***
 * 路由配置
 * @param builder
 * @return
 */
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {

    return builder.routes()
        .route("hailtaxi-driver", r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
        .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
        .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
        .build();
}

在真實場景中,基于組態檔的方式更直觀、簡介,但代碼的路由配置是更強大,可以實作很豐富的功能,可以把路由規則存在資料庫中,每次直接從資料庫中加載規則,這樣的好處是可以動態重繪路由規則,通常應用于權限系統動態配置,

spring: 
	cloud: 
		gateway:
          #路由配置
          routes:
            #唯一識別符號
            - id: hailtaxi-driver
              uri: lb://hailtaxi-driver
              #路由斷言
              predicates:
                - Path=/driver/**
            #唯一識別符號
            - id: hailtaxi-order
              uri: lb://hailtaxi-order
              #路由斷言
              predicates:
                - Path=/order/**
            #唯一識別符號
            - id: hailtaxi-pay
              uri: lb://hailtaxi-pay
              #路由斷言
              predicates:
                - Path=/pay/**

3.2.4 Gateway-Predicate

上面路由匹配規則中我們都用了- Path方式,其實就是路徑匹配方式,除了路徑匹配方式,Gateway還支持很多豐富的匹配方式,我們對這些方式分別進行講解,

關于Predicate學習地址,可以參考官網:

https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories

或者:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#gateway-request-predicates-factories

routes下面的屬性含義如下:

id:我們自定義的路由 ID,保持唯一

uri:目標服務地址

predicates:路由條件,Predicate 接受一個輸入引數,回傳一個布林值結果,該屬性包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非)

Predicate 來源于 Java 8,Predicate 接受一個輸入引數,回傳一個布林值結果,該介面包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非),

在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實作了各種路由匹配規則,通過 Header、請求引數等不同的條件來作為條件匹配到對應的路由,

下面的一張圖(來自網路)總結了 Spring Cloud 內置的幾種 Predicate 的實作:

file

我們在這里講解幾個斷言匹配 方式,

Cookie:

Gateway的Cookie匹配接收兩個引數:一個是 Cookie name ,一個是正則運算式,路由規則就是通過獲取對應的 Cookie name 值和正則運算式去匹配,如果匹配上就會執行路由,如果沒有匹配上則不執行,如下配置:

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/driver/**
        - Cookie=username,itheima

這里表示請求攜帶了cookie為username的資料,并且值為itheima,就允許通過,

Header 匹配:

Header 匹配 和 Cookie 匹配 一樣,也是接收兩個引數,一個 header 中屬性名稱和一個正則運算式,這個屬性值和正則運算式匹配則執行,配置如下:

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/driver/**
        - Header=token,^(?!\d+$)[\da-zA-Z]+$

上面的匹配規則,就是請求頭要有token屬性,并且值必須為數字和字母組合的正則運算式,例如攜帶token=19and30就可以通過訪問,

請求方式匹配:

通過請求的方式是 POST、GET、PUT、DELETE 等進行路由,配置如下:

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/driver/**
        - Method=GET,POST

通過yml的完整代碼如下(注釋掉java的配置

server:
  port: 8001
spring:
  application:
    name: hailtaxi-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    #Consul配置
    consul:
      host: 127.0.0.1
      port: 8500
      discovery:
        #注冊到Consul中的服務名字
        service-name: ${spring.application.name}
        #注冊的服務的實體 Id,最好不要重復,這里參考官網建議的方式 帶亂數 默認:應用名:port
        #instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring.application.i nstance_id:${random.value}}}
        # 自定義實體id為:應用名:ip:port
        instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
        prefer-ip-address: true
        # 開啟服務注冊
        register: true
        # 開啟服務發現
        enabled: true
        #2 分鐘之后健康檢查未通過取消注冊
        health-check-critical-timeout: 2m
        #consul 健康檢查的輪詢周期
        health-check-interval: 10s
    gateway:
      #路由配置
      routes:
        #唯一識別符號
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由斷言
          predicates:
            - Path=/driver/**
            - Cookie=username,itheima
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
        #唯一識別符號
        - id: hailtaxi-order
          uri: lb://hailtaxi-order
          #路由斷言
          predicates:
            - Path=/order/**
        #唯一識別符號
        - id: hailtaxi-pay
          uri: lb://hailtaxi-pay
          #路由斷言
          predicates:
            - Path=/pay/**
            

3.2.5、斷言原始碼剖析

Cookie斷言來說,首先看它的體系結構

file

public class CookieRoutePredicateFactory
		extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {

	/**
	 * Name key.
	 */
	public static final String NAME_KEY = "name";

	/**
	 * Regexp key.
	 */
	public static final String REGEXP_KEY = "regexp";

	public CookieRoutePredicateFactory() {
		super(Config.class);
	}
	
    /*
      通過shortcutFieldOrder方法設定Config配置類中的屬性,需要根據具體的規則來設定
      通過shortcutType方法獲取具體規則,具體參看:org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType
      規則包括以下幾種:
		DEFAULT : 按照shortcutFieldOrder順序依次賦值
    */
	@Override
	public List<String> shortcutFieldOrder() {
		return Arrays.asList(NAME_KEY, REGEXP_KEY);
	}

	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
				List<HttpCookie> cookies = exchange.getRequest().getCookies()
						.get(config.name);
				if (cookies == null) {
					return false;
				}
				for (HttpCookie cookie : cookies) {
					if (cookie.getValue().matches(config.regexp)) {
						return true;
					}
				}
				return false;
			}

			@Override
			public String toString() {
				return String.format("Cookie: name=%s regexp=%s", config.name,
						config.regexp);
			}
		};
	}
	
    /*
     內部配置類是用來接收在組態檔中配置的引數的
      routes:
        #唯一識別符號
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由斷言
          predicates:
            - Cookie=username,itheima
    */
	@Validated
	public static class Config {

		@NotEmpty
		private String name;

		@NotEmpty
		private String regexp;

		public String getName() {
			return name;
		}

		public Config setName(String name) {
			this.name = name;
			return this;
		}

		public String getRegexp() {
			return regexp;
		}

		public Config setRegexp(String regexp) {
			this.regexp = regexp;
			return this;
		}

	}

}

盡管Spring Cloud Gateway已經包含了很多路由匹配規則,有時候我們需要開發自定義路由匹配規則來滿足需求,下面簡單的介紹一下如何自定義路由匹配規則,

案例

需求:轉發帶token的請求到hailtaxi-drvier服務中,這里定義請求帶token是指包含某個請求頭的請求,至于是什么請求頭可以由配置指定

1、修改組態檔

    gateway:
      #路由配置
      routes:
        #唯一識別符號
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由斷言
          predicates:
			# 自定義一個Token斷言,如果請求包含Authorization的token資訊則通過
            - Token=Authorization

2、創建 RoutePredicateFactory

斷言工廠默認命名規則必須按照"名稱"+RoutePredicateFactory,如上TokenRoutePredicateFactory的斷言名稱為Token

@Slf4j
@Component // 要交給spring容器管理
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> {

    public TokenRoutePredicateFactory() {
        super(Config.class);
    }

    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 列印組態檔引數值
            String headerName = config.getHeaderName();
            HttpHeaders headers = exchange.getRequest().getHeaders();
            List<String> header = headers.get(headerName);
            log.info("Token Predicate headers:{}", header);
            // 斷言回傳的是boolean值
            return header!=null && header.size()>0;
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("headerName");//指定組態檔中加載到的配置資訊應填充到Config的哪個屬性上
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;
    }

    @Data
    public static class Config { //static class
        private String headerName;//存盤從組態檔中加載的配置
    }
}

啟動測驗:http://localhost:8001/driver/info/1

3.3 Gateway過濾器

Spring Cloud Gateway根據作用范圍劃分為GatewayFilterGlobalFilter,二者區別如下:

  • GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters配置在全域,作用在所有路由上;gateway內置了多種過濾器工廠,配套的過濾器可以直接使用,如下圖所示:

    file

    file

  • GlobalFilter : 全域過濾器,不需要在組態檔中配置,作用在所有的路由上,最終通過GatewayFilterAdapter包裝成GatewayFilterChain可識別的過濾器,它為請求業務以及路由的URI轉換為真實業務服務的請求地址的核心過濾器,不需要配置,系統初始化時加載,并作用在每個路由上,

file

過濾器作為Gateway的重要功能,常用于請求鑒權、服務呼叫時長統計、修改請求或回應header、限流、去除路徑等等,

關于Gateway過濾器的更多使用,大家可以參考官方地址:

https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

或者:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories

3.3.1 過濾器分類

默認過濾器:出廠自帶,實作好了拿來就用,不需要實作
  全域默認過濾器
  區域默認過濾器
  
自定義過濾器:根據需求自己實作,實作后需配置,然后才能用哦,
  全域過濾器:作用在所有路由上,
  區域過濾器:配置在具體路由下,只作用在當前路由上,

默認過濾器十好幾個,常見如下:

過濾器名稱 說明 對應的類 父類
AddRequestHeader 對匹配上的請求加上Header AddRequestHeaderGatewayFilterFactory AbstractNameValueGatewayFilterFactory
AddRequestParameters 對匹配上的請求路由 AddRequestHeaderGatewayFilterFactory AbstractNameValueGatewayFilterFactory
AddResponseHeader 對從網關回傳的回應添加Header AddResponseHeaderGatewayFilterFactory AbstractNameValueGatewayFilterFactory
StripPrefix 對匹配上的請求路徑去除前綴 StripPrefixGatewayFilterFactory AbstractGatewayFilterFactory

3.3.2 默認過濾器的使用

所謂默認過濾器就是系統自帶的,有很多,這里簡要說明幾個:(通過java配置,注釋掉yaml配置

1)添加回應頭

AddResponseHeaderGatewayFilterFactory 屬于 GatewayFilter

對輸出回應頭設定屬性,比如對輸出的回應設定其頭部屬性名稱為:X-Response-Default-MyName , 值為itheima

修改組態檔,配置如下:

spring:
  cloud:
    gateway:
     # 配置全域默認過濾器 作用在所有路由上,也可單獨為某個路由配置
      default-filters:
      # 往回應過濾器中加入資訊
        - AddResponseHeader=X-Response-Default-MyName,itheima

請求http://localhost:8001/driver/info/1,回應資料添加了X-Response-Default-MyName: itheima,如下圖:

file

2)前綴處理

在專案中做開發對接介面的時候,我們很多時候需要統一API路徑,比如統一以/api開始的請求呼叫hailtaxi-driver服務,但真實服務介面地址又沒有/api路徑,我們可以使用Gateway的過濾器處理請求路徑,

在gateway中可以通過配置路由的過濾器StripPrefix實作映射路徑中的前綴處理,我們來使用一下該過濾器,再進一步做說明,

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/api/driver/**
        filters:
          - StripPrefix=1

此處- StripPrefix=1表示真實請求地址是當前用戶請求以/api開始的uri中去除第1個路徑/api.

上面配置最終執行如下表:

配置 路由地址 訪問地址
StripPrefix=1 http://localhost:8001/api/driver/info/2 http://localhost:18081/driver/info/2
StripPrefix=2 http://localhost:8001/api/suri/driver/info/2 http://localhost:18081/driver/info/2

有時候為了簡化用戶請求地址,比如用戶請求http://localhost:8001/info/1我們想統一路由到http://localhost:18081/driver/info/1,可以使用PrefixPath過濾器增加前綴,

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/**
        filters:
          - PrefixPath=/driver

上面配置最終執行如下表:

配置 路由地址 訪問地址
- PrefixPath=/driver http://localhost:8001/info/2 http://localhost:18081/driver/info/2

3.3.3自定義GatewayFilter

1、實作GatewayFilter介面

GatewayFilter 一般作用在某一個路由上,需要實體化創建才能使用,區域過濾器需要實作介面GatewayFilter、Ordered

創建com.itheima.filter.PayFilter代碼如下:

public class PayFilter implements GatewayFilter,Ordered {

    /***
     * 過濾器執行攔截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("GatewayFilter攔截器執行---pre-----PayFilter");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            System.out.println("GatewayFilter攔截器執行---post-----PayFilter");
        }));
    }

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

使用區域過濾器:(使用下面RouteLocator的時候,組態檔中的路由記得注釋或洗掉)

/***
 * 路由配置
 * @param builder
 * @return
 */
 @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("hailtaxi-driver", r -> r.path("/api/driver/**")
                        .and().cookie("username","itheima")
                        .and().header("token","123456")
                                .filters(f->f.filter(new PayFilter()).addResponseHeader("X-Response-Default-MyName", "itheima")
                                        .addRequestHeader("myheader", "1234567")
                                .stripPrefix(1)
                                )
                       // .and().method(HttpMethod.POST)
                        .uri("lb://hailtaxi-driver")
                        //.filter(new PayFilter())
                 )
                .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
                .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
                .build();
    }

為了更好看到效果,我們在RouterFilter添加System.out.println("GlobalFilter攔截器執行");再訪問測驗,

訪問:http://localhost:8001/api/driver/info/1,注意使用postman發送請求時添加請求頭,添加cookie,

2、繼承GatewayFilterFactory

如果定義區域過濾器,想在組態檔中進行配置來使用,可以繼承AbstractGatewayFilterFactory<T>抽象類或者AbstractNameValueGatewayFilterFactory

整個體系結構為:

file

這兩個抽象類的區別就是前者接收一個引數(像StripPrefix和我們創建的這種),后者接收兩個引數(像AddResponseHeader)

代碼的撰寫可以參考:StripPrefixGatewayFilterFactoryAddRequestHeaderGatewayFilterFactory

過濾器工廠默認命名規則必須按照"名稱"+GatewayFilterFactory`,如上StripPrefixGatewayFilterFactory的過濾器名稱為StripPrefix

2.1、繼承AbstractGatewayFilterFactory

需求:

在網關中統一支付方式,撰寫一個過濾器:PayMethodGatewayFilterFactory

1、撰寫過濾器

@Slf4j
@Component //一定要將其交給spring容器管理
public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory<PayMethodGatewayFilterFactory.Config> {

    public PayMethodGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String paymethod = config.getPayMethod();
            String msg = config.getMsg();
            log.info("PayMethodGatewayFilterFactory 加載到的配置資訊為:{}---{}",paymethod,msg);
            //將paymethod添加到請求頭中
            exchange.getRequest().mutate().header("paymethod",paymethod);
            return chain.filter(exchange);
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return  Arrays.asList("payMethod","msg");//指定從yml中提前出來的配置資訊填充到配置類中哪個屬性,按規則配置
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;//默認規則
    }

    /**
     * 加載從yml中提取出來的配置資訊
     */
    @Data
    public static class Config {
        private String payMethod;
        private String msg;
    }
}

2、組態檔中使用如下:

    gateway:
      #路由配置
      routes:
        #唯一識別符號
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由斷言
          predicates:
            - Path=/driver/**
            - Cookie=username,itheima
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
            - Token=Authorization
          filters:
            - PayMethod=alipay,業務整合

再次測驗,查看hailtaxi-driver 服務接收到請求后是否多了paymethod請求頭資訊

2.2、繼承AbstractNameValueGatewayFilterFactory

直接查看AddRequestHeaderGatewayFilterFactory 原始碼,分析即可!

3.3.4 自定義GlobalFilter

定義全域過濾器需要實作GlobalFilter,Ordered介面:

GlobalFilter:過濾器攔截處理方法
Ordered:過濾器也有多個,這里主要定義過濾器執行順序,里面有個方法getOrder()會回傳過濾器執行順序,回傳值越小,越靠前執行

需求

我們創建全域過濾器并完成常見業務用戶權限校驗,如果請求中有帶有一個名字為token的請求引數,則認為請求有效放行,如果沒有則攔截提示授權無效,

創建全域過濾器:com.itheima.filter.RouterFilter,代碼如下:

@Component
public class RouterFilter implements GlobalFilter,Ordered {

    /***
     * 路由攔截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("RouterFilter----------------");
        //獲取請求引數
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        //如果token為空,則表示沒有登錄
        if(StringUtils.isEmpty(token)){
            //沒登錄,狀態設定403
            exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
            //結束請求
            return exchange.getResponse().setComplete();
        }
        //放行
        return chain.filter(exchange);
    }

    /***
     * 攔截器順序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

此時請求,我們不攜帶token引數,效果如下:

file

我們攜帶token引數則可以正常訪問,效果如下:

file

3.4 跨域配置

出于瀏覽器的同源策略限制,同源策略(Sameoriginpolicy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響,可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實作,同源策略會阻止一個域的javascript腳本和另外一個域的內容進行互動,所謂同源(即指在同一個域)就是兩個頁面具有相同的協議(protocol),主機(host)和埠號(port),

在Spring Cloud Gateway中配置跨域是非常簡單的,如下面application.yml所示:

    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT

但如果涉及到Cookie跨域,上面的配置就不生效了,如果涉及到Cookie跨域,需要創建CorsWebFilter過濾器,代碼如下:

/**
 * 配置跨域
 * @return
 */
@Bean
public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    // cookie跨域
    config.setAllowCredentials(Boolean.TRUE);
    config.addAllowedMethod("*");
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    // 配置前端js允許訪問的自定義回應頭
    config.addExposedHeader("Authorization");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", config);
    return new CorsWebFilter(source);
}

3.5 限流

網關可以做很多的事情,比如,限流,當我們的系統 被頻繁的請求的時候,就有可能 將系統壓垮,所以 為了解決這個問題,需要在每一個微服務中做限流操作,但是如果有了網關,那么就可以在網關系統做限流,因為所有的請求都需要先通過網關系統才能路由到微服務中,

3.5.1 漏桶演算法講解

file

漏桶演算法是常見的限流演算法之一,我們講解一下漏桶演算法:

1)所有的請求在處理之前都需要拿到一個可用的令牌才會被處理;
2)根據限流大小,設定按照一定的速率往桶里添加令牌;
3)桶設定最大的放置令牌限制,當桶滿時、新添加的令牌就被丟棄或者拒絕;
4)請求達到后首先要獲取令牌桶中的令牌,拿著令牌才可以進行其他的業務邏輯,處理完業務邏輯之后,將令牌直接洗掉;
5)令牌桶有最低限額,當桶中的令牌達到最低限額的時候,請求處理完之后將不會洗掉令牌,以此保證足夠的限流

漏桶演算法的實作,有很多技術,Guaua是其中之一,redis客戶端也有其實作,

3.5.2 限流案例

spring cloud gateway 默認使用redis的RateLimter限流算法來實作,外面來簡要實作一下:

1、引入依賴

首先需要引入redis的依賴:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

同時不要忘記Redis配置:

  redis:
    host: 127.0.0.1
    port: 6379

2、定義KeyResolver

在Application引導類中添加如下代碼,KeyResolver用于計算某一個型別的限流的KEY也就是說,可以通過KeyResolver來指定限流的Key,

我們可以根據IP來限流,比如每個IP每秒鐘只能請求一次,在GatewayApplication定義key的獲取,獲取客戶端IP,將IP作為key,如下代碼:

/***
 * IP限流
 * @return
 */
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {
    return new KeyResolver() {
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {
            //獲取遠程客戶端IP
            String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            System.out.println("hostName:"+hostName);
            return Mono.just(hostName);
        }
    };
}

在路由中配置如下:

    gateway:
      #路由配置
      routes:
        #唯一識別符號
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由斷言
          predicates:
            - Path=/driver/**
            - Cookie=username,itheima
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
            - Token=Authorization
          filters:
            - PayMethod=alipay,業務整合
            - name: RequestRateLimiter #請求數限流 名字不能隨便寫 ,使用默認的facatory
              args:
                key-resolver: "#{@ipKeyResolver}"
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1

引數說明:

redis-rate-limiter.replenishRate是您希望允許用戶每秒執行多少請求,而不會丟棄任何請求,這是令牌桶填充的速率
redis-rate-limiter.burstCapacity是指令牌桶的容量,允許在一秒鐘內完成的最大請求數,將此值設定為零將阻止所有請求,
key-resolver: “#{@ipKeyResolver}” 用于通過SPEL運算式來指定使用哪一個KeyResolver.

如上配置:
表示 一秒內,允許 一個請求通過,令牌桶的填充速率也是一秒鐘添加一個令牌,
最大突發狀況 也只允許 一秒內有一次請求,可以根據業務來調整 ,

我們請求http://localhost:8001/driver/info/1?token=aa執行測驗,效果如下:

file

本文由傳智教育博學谷 - 狂野架構師教研團隊發布
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力
轉載請注明出處!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/498680.html

標籤:Java

上一篇:封裝繼承多型

下一篇:Java Long類highestOneBit()方法具有什么功能呢?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more