一樣是網關gateway取代了zuul,咱們項目使用了這麼多gateway特性!你有沒有中標|Java 開發實戰

這是我參與更文挑戰的第1天,活動詳情查看: 更文挑戰html

本文正在參加「Java主題月 - Java 開發實戰」,詳情查看 活動連接java

相關文章

主流的四種限流策略,我均可以經過redis實現react

分佈式系列之網關zuul包攬全局--不再用東拼西湊各個接口啦web

特性

gateway的誕生是由於zuul2.0一直跳票,既然這樣gateway能夠說是zuul的替代品。既然是替代品功能確定是包含了zuul的。redis

  • 上面zuul的章節主要介紹了網關的動態路由。gateway確定也是支持的。
  • 除了路由外zuul還有四種過濾器一樣gateway也有相應的過濾器
  • zuul內部攜帶hystrix , gateway除了和hystrix整合實現熔斷、降級、限流外,內部基於令牌桶實現限流
  • zuul、gateway都支持服務發現進行路由轉發

前置條件

SpringCloud Gateway是基於webflux的非阻塞式的網關。 因此gateway的環境要求 Springboot 2.x+、spring webflux、project reactor算法

由於是非阻塞式,因此和咱們以前的阻塞式框架會有所不一樣。spring

專業名詞

名詞 解釋
Route 網關的基本組成部分。它由一個ID、一個目標URI、一組謂詞和一組過濾器定義。若是聚合謂詞爲真,則匹配路由
Predicate 這是一個Java 8函數謂詞。輸入類型是Spring Framework serverwebeexchange。這容許您匹配HTTP請求中的任何內容,好比頭或參數
Filter 這些是使用特定工廠構造的GatewayFilter實例。發送下游請求以前或以後修改請求和響應

快速入門

點我官網快速開始api

  • 新建模塊geteway 並制定端口9091數組

  • 引入座標安全

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
複製代碼
  • 配置路由
spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
複製代碼
  • 接口測試 : 咱們訪問http://localhost:9091/payment/get/1 會出現8001payment的信息

  • 下面咱們詳細來看看gateway的路由規則

路由規則

Springcloud網關匹配路由做爲Spring WebFlux HandlerMapping基礎設施的一部分。Spring Cloud Gateway包括許多內置的路由前置工廠。全部這些都匹配HTTP請求的不一樣屬性。咱們能夠將多個路由謂詞工廠與邏輯和語句組合在一塊兒

image-20210506135529574

  • 咱們也能夠看出springcloud gateway內置了不少匹配工廠。咱們上面的案列就是經過PathRoutePredicateFactory來實現的

path

spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
複製代碼

query

spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
            - Query=green
複製代碼
  • http://localhost:9091/payment/get/1 接口將訪問不了數據,必須添加green參數 http://localhost:9091/payment/get/1?green

  • 光有參數仍是不行,加入咱們要求green參數必須是數組。咱們能夠以下配置

spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
            - Query=green,\d+
複製代碼
  • http://localhost:9091/payment/get/1?green=123s 不能訪問 , http://localhost:9091/payment/get/1?green=123 則能夠

datetime

  • 當咱們在開發限時搶購接口是就能夠藉助datetime功能來實現
spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
            - Query=green,\d+
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
複製代碼

ip

- RemoteAddr=10.0.20.132/140
複製代碼
  • 關於這個遠程地址咱們實際上就是填寫ip , 後面的斜槓表示一個範圍ip , 上面的配置表示10.0.20.132-10.0.20.140 這個段的ip均可以訪問

cookie

- Cookie=zxhtom, hello
複製代碼
  • 請求中攜帶zxhtom=hello , 的cookie能夠訪問

服務發現

  • 在zuul中整合服務發現後有默認的路由規則的。一樣的gateway也能夠接入服務發現這裏仍是以eureka爲列。

  • 這裏有關服務發現的配置我就再也不贅述了。在eureka專題已經介紹了,在hystrix、zuul等專題頁有相關的配置,我就直接貼出gateway的開啓配置

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
複製代碼
  • 默認的路由規則是基於在eureka中註冊名進行訪問 。 好比訪問payment 就是以下地址http://localhost:9091/cloud-payment-service/payment/get/2
  • 內部也是ribbon進行負載均衡的。有個小問題是gateway啓動時獲取服務列表後續好像沒有在更新服務列表。

過濾器

  • 關於gateway內置過濾器真的是不少不少。咱們這裏也不能一個一個的舉例。下面咱們看看和zuul對應的幾個過濾器。在看以前咱們先大概看看gateway內置有哪些

image-20210506184914527

  • 除了局部過濾器,gateway還內置幾個全局過濾器

image-20210506185225830

RewiritePathGatewayFilterFactory

  • 還記得咱們在zuul專題中實現了默認路由轉發規則嗎,就是將CLOUD-PAYMENT-SERVICECLOUD-ORDER-SERVICE 這些微服務默認代理爲paymentorder 的服務前綴。在gateway中他也爲咱們提供了相同的功能即服務轉發。可是他是針對具體的微服務的。咱們能夠經過自定義全局過濾器來實現zuul中的功能。下面咱們來試試經過RewiritePathGatewayFilterFactory 實現它

  • 爲了突出核心配置,這裏我只放部分配置,須要完整的能夠參考上下文。或者直接看源碼。源碼會在文章末尾放出

- id: order
          uri: lb://CLOUD-ORDER-SERVICE
          predicates:
            - Query=green
          filters:
            - RewritePath=/order/(?<segment>.*), /$\{segment}
複製代碼
  • 和zuul的配置差很少,經過正則匹配咱們的請求,而後將咱們須要的部分進行抽取而後路由到真實服務進行調用

PrefixPathGatewayFilterFactory

  • 和上面``RewiritePathGatewayFilterFactory 相似而又不一樣的是,PrefixPathGatewayFilterFactory 是在咱們匹配接口上統一加上前綴。好比以下配置
- id: pre_route
          uri: http://localhost:8001
          predicates:
            - Path=/getTimeOut/**
          filters:
            - PrefixPath=/payment
複製代碼
  • http://localhost:9091/getTimeOut/123 訪問這個接口的時候,咱們首先經過routes 中配置匹配到pre_route 而後在接口前添加payment而後路由到真實服務上。 這裏須要注意的是咱們的匹配是從上至下的因此咱們配置的時候注意下順序

StripPrefixGatewayFilterFactory

  • 這個過濾器的功能就是將咱們的接口進行分割,而後在路由。其實gateway爲咱們提供了不少的過濾器使用起來也是很方便的。咱們基本上看官網提供的案列咱們就可以知道如何使用了。重要的咱們得了解他們內部的設計。設計纔是王者
- id: pre_route
          uri: http://localhost:8001
          predicates:
            - Path=/zxhtom/**
          filters:
            - StripPrefix=2
複製代碼
  • 訪問http://localhost:9091/zxhtom/lqj/payment/getTimeOut/123 實際上會轉發到http://localhost:8001/payment/getTimeOut/123上。注意咱們uri的協議,若是是lb表示是服務發現。這裏咱們配置的是單節點

SetPathGatewayFilterFactory

- id: pre_route3
          uri: http://localhost:8001
          predicates:
            - Path=/hello/{segment}
          filters:
            - SetPath=/payment/get/{segment}
複製代碼
  • 說白了仍是將請求從新轉發。只不過這邊更加的生硬。上述就是將http://localhost:9091/hello/123 轉發到http://localhost:8081/payment/get/123

自定義網關過濾器

  • 上面咱們簡單瞭解了gateway爲咱們提供的過濾器。可是內置的永遠是知足不了全部的需求的。不論是爲了應對需求仍是知足本身的虛榮心。咱們都應該來看看若是實現本身的過濾器

GatewayFilterFactory

image-20210506190710838

  • gateway內置了不少網關過濾器。咱們只須要參照他們內置的過濾器實現就能夠了。
  • 不知道你有沒有注意到類如RewritePathGatewayFilterFactory 這些過濾器在配置的時候是配置成RewritePath 後面的GatewayFilterFactory是沒有的。
  • 這是爲何呢,咱們先不關這個咱們依葫蘆畫瓢。
  • 如今咱們有個需求須要實現登錄驗證過濾器。實現邏輯簡單點就驗證用戶名和密碼是否匹配。用戶名密碼在地址中配置

新建過濾器類

@Component
public class LoginPathGatewayFilterFactory extends AbstractGatewayFilterFactory<LoginPathGatewayFilterFactory.Config> {
    public LoginPathGatewayFilterFactory() {
        super(Config.class);
    }
    @Override
    public GatewayFilter apply(Config config) {
        return (exchage,chain)->{
            String userName = config.getUserName();
            String password = config.getPassword();
            String requestUserName = exchage.getRequest().getQueryParams().getFirst("userName");
            String requestPassword = exchage.getRequest().getQueryParams().getFirst("password");
            if (userName.equals(requestUserName) && password.equals(requestPassword)) {
                return chain.filter(exchage);
            } else {
                throw new RuntimeException("用戶名錯誤密碼。。。。");
            }
        };
    }
    public static class Config {

        private String userName;

        private String password;

        public String getUserName() {
            return userName;
        }

        public Config setUserName(String userName) {
            this.userName = userName;
            return this;
        }

        public String getPassword() {
            return password;
        }

        public Config setPassword(String password) {
            this.password = password;
            return this;
        }
    }
}
複製代碼

新增配置

- id: pre_route4
          uri: http://localhost:8001
          predicates:
            - Path=/login/{segment}
          filters:
            - name: LoginPath
              args:
                userName: zxhtom
                password: test
            - SetPath=/payment/get/{segment}
複製代碼

測試

image-20210506193533580

  • 當咱們的用戶名和密碼不一致時就會拋出異常。若是用戶名和密碼和咱們指定的帳戶相同那麼就會放心至第二個過濾器進行路由轉發。

GatewayFilter

  • 在上面第一種方式中咱們可以觀察到最終是生成GatewayFilter 對象。實際上GatewayFilter 纔是真正參與過濾的對象。

構建

@Component
public class CustomLoginGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("我進過濾器啦。。。");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
複製代碼

註冊

@Configuration
public class RouteConfig {
    @Bean
    public RouteLocator customRouteLocator(CustomLoginGatewayFilter customLoginGatewayFilter,RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route", r -> r.path("/baidu")
                        .uri("https://www.baidu.com")
                .filter(customLoginGatewayFilter))
                .build();
    }
}
複製代碼

測試

image-20210506194442468

自定義全局過濾器

image-20210507092458434

  • 仍是那句話,想要自定義得看內置是如何實現的。諾、WebsocketRoutingFilter 是gateway內置的,他的實現和網關過濾器其中一種方式同樣。
@Slf4j
@Component
public class GlobalCustomRoutingFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("我是全局過濾器。。。。。。");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
複製代碼
  • 編寫java類註冊到spring中,咱們在訪問咱們的gateway路由接口就會發現有日誌打印了。固然這裏只是演示全局過濾器生效。具體在全局過濾器咱們能夠用來作權限驗證等等操做。
  • 在全局驗證後咱們能夠將登錄用戶信息寫入到cookie中或者經過添加參數的方式傳遞到下游

過濾器名稱

image-20210507100537676

  • 還記得咱們在網關過濾器那邊說爲何自定義名字要那麼定義嗎。看看上面這段源碼你就理解了。在將過濾器已鍵值對註冊到過濾器容器中

image-20210507101527587

限流

  • 關於限流筆者以前經過redis分別實現目前主流的四種限流算法。有興趣的能夠閱讀下,閱讀結束別忘記回到本文繼續觀看gateway哦。別走太遠!!!

  • 上面咱們主要在介紹gateway的用法即合成方向。可是做爲網站的門戶性接口流量比其餘服務都大的不少。其餘服務正常狀況咱們會依賴於hystrix 來實現對服務的降級等操做。一樣咱們也能夠在網關層面上加入hystrix來實現服務的高可用。 可是除了hystrix之外, gateway自己內置了限流算法。下面咱們來簡單實現下限流。

  • 提到網關咱們就必定繞不開限流。網關的做用就是代理可是並非沒有條件的任性代理。爲何保護咱們的模塊在網關中會對下游服務進行限流。有些接口處於安全和穩定考慮都會限制流量的

  • gateway實現限流也很簡單。只須要咱們引入redis相關模塊在使用內置過濾器就能夠了

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>	
複製代碼
@Component
public class HostAddrKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        //根據服務地址限流
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
    @Bean
    KeyResolver apiKeyResolver() {
            //按URL限流
            return exchange -> Mono.just(exchange.getRequest().getPath().toString());
            }

    @Bean
    KeyResolver userKeyResolver() {
        //按用戶限流
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userName"));
    }
}
複製代碼

image-20210531172056157

  • redis-rate-limiter.replenishRate : 令牌桶生成令牌的速度
  • redis-rate-limiter.burstCapaciry: 令牌桶容量
  • 下面咱們看看redis中實時存儲數據狀況。上面咱們經過jmeter等壓測工具進行壓力測試就會出現以下數據

image-20210531171802520

計數器

  • 計數器又分爲固定時間窗口和滑動時間窗口兩種策略。二者的區別其實就是時間間隔不同。本質上是沒有區別。
  • 可是由於滑動時間窗口時間間隔很短,給咱們形成一種二者徹底不同的假象

固定時間窗口

image-20210508103957917

滑動時間窗口算法

漏銅

  • 漏桶算法就是提早準備了一個請求池,可以進入請求池就能夠等待資源的分配。這樣就解決了計數沒法面對大流量的狀況

令牌桶

  • 令牌桶是在漏桶基礎上升級而來。咱們仔細想想正常請求仍是須要花費必定時間的有的重要接口甚至須要1~10秒執行時間。可是咱們生成令牌只是生成一串字符。和生成令牌相比接口的執行顯然慢的多的多
  • 不知道讀者有沒有想過這樣一種狀況。漏桶算法在請求池滿了以後忽然遇到大流量這個時候該怎麼辦。這個時候漏桶算法就只能無情的拒絕多餘的請求。
  • 而令牌桶則不同了。令牌桶也有一樣的池,可是內部存儲的是生成好的令牌,一樣在極限狀況下若是令牌桶慢了遇到大流量會怎麼樣。
  • 在漏桶算法中遇到大流量就只能拒絕並且請求池狀態跟更新很慢。可是令牌桶滿了有大流量過來很快就會拿走令牌。接口的執行時間跟令牌桶就沒有直接關係了。這個時候令牌桶中狀態更新的很快。因此令牌桶更加能面對突如其來的大流量

整合hystrix

  • 還記得咱們以前的hystrix服務熔斷、降級專欄嗎。仔細想一想做爲網關是否是比其餘接口流量更大呢?那麼如何對咱們網關的接口進行服務熔斷等操做呢?
  • 筆者這裏參考了寫資料僅實現全局後備的實現

功能接入

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
複製代碼

image-20210531170456893

  • 接入座標後再咱們上面實現的基礎上咱們直接添加過濾器就能夠了。最後會將異常重定向到咱們事先寫好的接口中。這個接口返回 咱們統一的數據結構
@RestController
@RequestMapping("/fallback")
public class FallbackController {

    @RequestMapping("")
    public String fallback(){
        return "error";
    }
}
複製代碼

總結

  • 好了!關於網關部分筆者斷斷續續經過三篇文章分別從不一樣框架,不一樣角度解讀。
  • zuul框架實現了咱們項目中經常使用到的網關代理功能、本文是經過gateway實現項目中的網關代理
  • 在加上前兩天經過redis分別實現四種主流限流策略問題。上面提到的兩篇文章收穫頗豐



努力加油

大哥大姐,留個贊在走唄!原創屬實不易

相關文章
相關標籤/搜索