Spring Cloud Gateway網關原理


依賴

compile 'org.springframework.cloud:spring-cloud-starter-gateway'
compile 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'

包結構

actuate中定義了一個叫GatewayControllerEndpoint的類,這個類提供一些對外的接口,可以獲取網關的一些信息,比如路由的信息,改變路由地址等等

config中定義了一些啟動時去加載的類,配置路由信息和讀取你的配置文件就在這里完成

discovery中定義了注冊中心相關的內容,包括注冊中心的路由等

event定義了一些事件他們都繼承自ApplicationEvent,對事件發布不了解的可以去看看spring的代碼

filter中定義了spring cloud gateway實現的一些過濾器

handler中定義了很多Predicate相關的Factory

route就是我們路由的相關

support是工具包

啟動流程

網關啟動第一步加載的就是去加載config包下的幾個類

1.> GatewayClassPathWarningAutoConfiguration.class:  如果DispatcherServlet存在,會給與警告,同樣的DispatcherHandler不存在也會警告。

注意不要引入spring-boot-starter-web的依賴,會報錯,因為gateway是基於spring-webflux開發的,他依賴的DispatcherHandler就和web里的DispatcherServlet是一樣的功能

2.> GatewayLoadBalancerClientAutoConfiguration.class:  gateway負載均衡的過濾器實現的加載,他將LoadBalancerClientFilter 注入到了容器中

3.>  GatewayAutoConfiguration.class: 注冊始初始化配置文件加載器、路由 轉發、過濾器、 心跳監測

4.> GatewayDiscoveryClientAutoConfiguration.class 注冊DiscoveryClientRouteDefinitionLocator.class,在構造方法中通過discoveryClient獲取服務發現中心的服務路由(存疑:容器啟動時只有當前服務的路由信息,而不是所有服務路由信息)

5.> RouteDefinitionRouteLocator.class實現RouteLocator接口(內部是使用PropertiesRouteDefinitionLocator.class)讀取GatewayProperties配置文件轉成路由RouteDefinition

6.> DiscoveryClientRouteDefinitionLocator.class實現RouteDefinitionLocator接口,getRouteDefinitions()時,通過discoveryClient轉換服務發現中心的服務路由

工作原理

客戶端向Spring Cloud Gateway發出請求。如果網關處理程序映射確定請求與路由匹配,則將其發送到網關Web處理程序。

該處理程序通過特定於請求的過濾器鏈運行請求。篩選器由虛線分隔的原因是,篩選器可以在發送代理請求之前和之后運行邏輯。

所有“前置”過濾器(Pre Filter)邏輯均被執行。然后發出代理請求。發出代理請求后,將運行“后”過濾器(Post Filter)邏輯。

執行順序類似棧LIFO(后進先出),U型順序。

HttpWebHandlerAdapter->RoutePredicateHandlerMapping->FilteringWebHandler

謂詞工廠/斷言

predicates Factory 配置 描述
After - After=2017-01-20T17:42:47.789-07:00[America/Denver] 在該日期時間之后發生的請求都將被匹配。
Before - Before=2017-01-20T17:42:47.789-07:00[America/Denver] 在該日期時間之前發生的請求都將被匹配。
Between - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] 在兩個日期時間之間發生的請求都將被匹配。
Cookie - Cookie=chocolate, ch.p 請求包含次cookie名稱且正則表達式為真的將會被匹配。
Header - Header=X-Request-Id, \d+ 請求包含次header名稱且正則表達式為真的將會被匹配。
Host - Host=**.somehost.org,**.anotherhost.org 使用Ant路徑匹配規則,.作為分隔符
Method - Method=GET 所有GET請求都將被路由
Path - Path=/foo/{segment},/bar/{segment} 路徑/foo/開頭或/bar/開頭的請求都將被匹配
Query

- Query=baz

- Query=foo, ba.

包含了請求參數 baz的都將被匹配

請求參數里包含foo參數,並且值匹配為ba.

RemoteAddr - RemoteAddr=192.168.1.1/24 請求的remote address 為 192.168.1.10則將被路由
Weight

routes:

 - id: weight_high

  uri: https://weighthigh.org

  predicates:

    - Weight=group1, 8

 - id: weight_low

  uri: https://weightlow.org

  predicates:

    - Weight=group1, 2

將大約80%的流量轉發到weighthigh.org,將大約20%的流量轉發weightlow.org

跨域

@Bean
public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedMethod("*");//支持所有方法
    config.addAllowedOrigin("*");//跨域處理 允許所有的域
    config.addAllowedHeader("*");//支持所有請求頭

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", config);//匹配所有請求

    return new CorsWebFilter(source);
}

 

過濾器工廠

  • 全局過濾器與其他2類過濾器相比,永遠是最后執行的;它的優先級只對其他全局過濾器起作用

  • 當默認過濾器與自定義過濾器的優先級一樣時,優先出發默認過濾器,然后才是自定義過濾器;同類型的過濾器,出發順序與他們在配置文件中聲明的順序一致

  • 默認過濾器與自定義過濾器使用同樣的order順序空間,即他們會按照各自的順序來進行排序 

GatewayFilter Factory

配置 描述
 AddRequestHeader

 filters:

  - AddRequestHeader=X-Request-Foo, Bar

 向下游請求的header頭中添加 x-request-foo:bar
 AddRequestParameter  - AddRequestParameter=foo, bar  向下游請求添加foo=bar請求參數
 AddResponseHeader  - AddResponseHeader=X-Response-Foo, Bar  向下游響應的header頭添加x-response-foo:bar
 CircuitBreaker

 - Hystrix=myCommandName

或者

filters:

  - name: CircuitBreaker

   args:

    name: myCircuitBreaker

    fallbackUri: forward:/inCaseOfFailureUseThis

  - RewritePath=/consumingServiceEndpoint, /backingServiceEndpoint

 斷路器過濾器;發生斷路時將請求轉發或者調用fallback處理

還可以將請求重新路由到外部應用程序中的控制器或處理程序

 PrefixPath  - PrefixPath=/mypath  所有匹配請求的路徑加前綴/mypath。因此,向/hello發送的請求將發送到/mypath/hello
 RequestRateLimiter

 filters:

  - name: RequestRateLimiter

  args:

    redis-rate-limiter.replenishRate: 10

    redis-rate-limiter.burstCapacity: 20

    redis-rate-limiter.requestedTokens: 1

令牌桶限流

每個用戶10的請求速率限制。

允許20個突發,

但是在下一秒中,只有10個請求可用

 RedirectTo  - RedirectTo=302, http://acme.org  發送一個302狀態碼和一個Location:http://acme.org header來執行重定向
 RemoveRequestHeader  - RemoveRequestHeader=X-Request-Foo  向下游請求的header頭中刪除X-Request-Foo
 RemoveResponseHeader  - RemoveResponseHeader=X-Request-Foo  向下游響應的header頭中刪除X-Request-Foo
 RewritePath  - RewritePath=/foo/(?<segment>.*), /$\{segment}  使用Java正則表達式重寫請求路徑
 RewriteResponseHeader  - RewriteResponseHeader=X-Response-Foo, , password=[^&]+, password=***  使用Java正則表達式重寫響應頭的值
 SetPath

predicates:

  - Path=/foo/{segment}
filters:

  - SetPath=/{segment}

 對於一個 /foo/bar請求,在做下游請求前,路徑將被設置為/bar
 SetResponseHeader  同上   同上
 SetStatus  同上   同上
 StripPrefix

predicates:

  - Path=/name/**
filters:

  - StripPrefix=1

 當通過網關發出/name/bar/foo請求時,向nameservice發出的請求將是http://nameservice/bar/foo
 Retry filters:
  - name: Retry
args:
  retries: 3
  statuses: BAD_GATEWAY
 retry filter 不支持body請求的重試,如通過body的POST 或 PUT請求
 RequestSize

filters:

  - name: RequestSize
args:

  maxSize: 5000000

 請求大小大於限制

例如:

@Component
public class IpFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 如果在忽略的url里,則跳過
        String path = replacePrefix(exchange.getRequest().getURI().getPath());
        String requestUrl = exchange.getRequest().getURI().getRawPath();

        if (ignore(path) || ignore(requestUrl)) {
            return chain.filter(exchange);
        }
        // 驗證token是否有效
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        try {
            String token = request.getHeaders().getFirst("token");
            //沒有token返回未認證
            if (token == null || token == "") {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            JWTUtils.verifyToken(token);
            return chain.filter(exchange);
        } catch (Exception e) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
    }

    private boolean ignore(String path) {
        return Arrays.stream(IGNOREURL).map(url -> url.replace("/**", "")).anyMatch(path::startsWith);
    }

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

負載均衡

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 啟用自動根據服務ID生成路由 lower-case-service-id: true # 設置路由的路徑為小寫的服務ID routes: - id: sso-service # 路由ID(一個路由配置一個ID) uri: lb://sso # 通過注冊中心來查找服務(lb代表從注冊中心獲取服務,並且自動開啟負載均衡 predicates: - Path=/auth/** # 匹配到的以/product開頭的路徑都轉發到product的服務,相當於訪問 lb://PRODUCT-SERVICE/** filters: - StripPrefix=1 # 去掉匹配到的路徑的第一段

LoadBalancerClientFilter :實現負載均衡的全局過濾器,內部實現是ribbon

權重負載

routes:
    - id: spring-cloud-client-demo uri: lb://spring-cloud-client-demo  predicates: - Path=/client/** - Weight=group1, 2 filters: - StripPrefix=1 - id: spring-cloud-client-demo1 uri: lb://spring-cloud-client-demo predicates: - Path=/client/** - Weight=group1, 8 filters: - StripPrefix=1

限流

默認:RequestRateLimiterGatewayFilterFactory限流過濾器和限流的實現類RedisRateLimiter使用令牌桶限流;

常用的限流算法有幾種:計數器算法、漏桶算法令牌桶算法

·計數算法適合流量突發情況(瞬間突發)

·令牌桶適合均速,無法獲取令牌的請求直接拒絕

·漏桶算法適合均速並且可以讓請求進行等待,不需要直接拒絕請求

計數器算法:

維護一個單位時間內的計數器(例如:設置1s內允許請求次數10次),表示為時間單位1秒內允許計數次數最高為10,每次請求計數器加1,當單位時間內計數器累加到大於設定的閾值(10),則之后的請求都被拒絕,直到單位時間(1s)已經過去,再將計數器重置為零,缺點:如果在單位時間1s內允許100個請求,在10ms已經通過了100個請求,那后面的990ms所接收到的請求都會被拒絕,我們把這種現象稱為“突刺現象”。

漏桶算法:

水(請求)先進入到漏桶里,漏桶以一定的速度出水(接口響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),然后就拒絕請求,可以看出漏桶算法能強行限制數據的傳輸速率。

令牌桶算法:

隨着時間流逝,系統會按恆定 1/QPS 時間間隔(如果 QPS=100,則間隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個 Token,如果沒有 Token 可拿了就阻塞或者拒絕服務。

自定義限流過濾:

public class UriKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getURI().getPath());
    }

}
//將這個類的Bean注冊到Ioc容器中
@Bean
public UriKeyResolver uriKeyResolver() {
    return new UriKeyResolver();
}

或者

//自定義限流
@Bean 
public KeyResolver hostAddrKeyResolver() { 
    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); 
}
spring:
  cloud:
    gateway:
      routes:
      - id: test-service
        uri: lb://test
        predicates:
        - Path=/test/**
        filters:
        - StripPrefix= 1
        - name: RequestRateLimiter #請求數限流 名字不能隨便寫 
          args: key-resolver: "#{@hostAddrKeyResolver}" #使用SpEL按名稱引用bean
            redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
            redis-rate-limiter.burstCapacity: 1 #令牌桶的容量,允許在一秒鍾內完成的最大請求數

降級

spring:
  cloud:
    gateway:
      routes:
      - id: test-service
        uri: lb://test
        predicates:
        - Path=/test/**
        filters:
        - StripPrefix= 1
        - name: Hystrix args: name: fallback # Hystrix的bean名稱
            fallbackUri: 'forward:/fallback' # Hystrix超時降級后調用uri地址
@RestController
@Slf4j
public class FallbackController {

    @RequestMapping(value = "/fallback")
    @ResponseStatus
    public Mono<Map<String, Object>> fallback(ServerWebExchange exchange) {
        Map<String, Object> result = new HashMap<>(3);
        result.put("code", 7002);
        result.put("data", null);
        Exception exception = exchange.getAttribute(ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR);
        ServerWebExchange delegate = ((ServerWebExchangeDecorator) exchange).getDelegate();
        log.error("接口調用失敗,URL={}", delegate.getRequest().getURI(), exception);
        if (exception instanceof HystrixTimeoutException) {
            result.put("msg", "接口調用超時");
        } else if (exception != null && exception.getMessage() != null) {
            result.put("msg", "接口調用失敗: " + exception.getMessage());
        } else {
            result.put("msg", "接口調用失敗");
        }
        return Mono.just(result);
    }
}

 重試

spring:
  cloud:
    gateway:
      routes:
      - id: test-service
        uri: lb://test
        predicates:
        - Path=/test/**
        filters:
        - StripPrefix= 1
        - name: Retry #重試
          args: retries: 1 #重試次數
            series: #不指定錯誤碼系列
            statuses: BAD_GATEWAY,INTERNAL_SERVER_ERROR,SERVICE_UNAVAILABLE #500,502狀態重試
            methods: GET,POST # 只有get和post接口重試

 


免責聲明!

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



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