微服務網關Spring Cloud Gateway全搞定


微服務網關Spring Cloud Gateway全搞定

一、微服務網關Spring Cloud Gateway

1.1 導引

文中內容包含:微服務網關限流10萬QPS、跨域、過濾器、令牌桶算法。

在構建微服務系統中,必不可少的技術就是網關了,從早期的Zuul,到現在的Spring Cloud Gateway,網關我們用的不可少。

今天我就將沉澱下來的所有與網關相關的知識,用一篇文章總結清楚,希望對愛學習的小夥伴們有所幫助。

本篇文章主要介紹網關跨域配置,網關過濾器編寫,網關的令牌桶算法限流【每秒10萬QPS】

首先我們來看什麼是網關

1.2 什麼是微服務網關Gateway?

This project provides a library for building an API Gateway on top of Spring WebFlux.

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

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

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

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

1.3 微服務為什麼要使用網關呢?

不同的微服務一般會有不同的網絡地址,而外部客戶端可能需要調用多個服務的接口才能完成一個業務需求,如果讓客戶端直接與各個微服務通信,會有以下的問題:

  1. 客戶端會多次請求不同的微服務,增加了客戶端的複雜性
  2. 存在跨域請求,在一定場景下處理相對複雜
  3. 認證複雜,每個服務都需要獨立認證
  4. 難以重構,隨著項目的迭代,可能需要重新劃分微服務。例如,可能將多個服務合併成一個或者將一個服務拆分成多個。如果客戶端直接與微服務通信,那麼重構將會很難實施
  5. 某些微服務可能使用了防火牆 / 瀏覽器不友好的協議,直接訪問會有一定的困難

以上這些問題可以藉助網關解決。

網關是介於客戶端和服務器端之間的中間層,所有的外部請求都會先經過 網關這一層。也就是說,API 的實現方面更多地考慮業務邏輯,而安全、性能、監控可以交由 網關來做,這樣既提高業務靈活性又不缺安全性,典型的架構圖如圖所示:


微服務網關Spring Cloud Gateway全搞定

1.4 微服務網關的優點

  • 安全 ,只有網關係統對外進行暴露,微服務可以隱藏在內網,通過防火牆保護。
  • 易於監控。可以在網關收集監控數據並將其推送到外部系統進行分析。
  • 易於認證。可以在網關上進行認證,然後再將請求轉發到後端的微服務,而無須在每個微服務中進行認證。
  • 減少了客戶端與各個微服務之間的交互次數
  • 易於統一授權。

1.5 總結

微服務網關就是一個系統,通過暴露該微服務網關係統,方便我們進行相關的鑑權,安全控制,日誌統一處理,易於監控的相關功能。

二、微服務網關搭建及配置

2.1 微服務網關微服務搭建

由於我們開發的系統 有包括前臺系統和後臺系統,後臺的系統給管理員使用。那麼也需要調用各種微服務,所以我們針對管理後臺搭建一個網關微服務。分析如下:


微服務網關Spring Cloud Gateway全搞定

搭建步驟:

(1)依賴座標pom.xml:

<code>
    org.springframework.cloud
    spring-cloud-starter-gateway


    org.springframework.cloud
    spring-cloud-starter-netflix-hystrix


    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client

複製代碼/<code>

(2)啟動引導類:GatewayApplication

<code>@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
複製代碼/<code>

(3)在resources下創建application.yml

<code>spring:
  application:
    name: apigateway
  cloud:
    gateway:
      routes:
      - id: open
        uri: lb://open
        predicates:
        - Path=/open/**
        filters:
        - StripPrefix= 1
      - id: system
        uri: lb://system
        predicates:
        - Path=/system/**
        filters:
        - StripPrefix= 1
server:
  port: 9999
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
複製代碼/<code>

參考官方手冊:

cloud.spring.io/spring-clou…

2.2 微服務網關跨域

在啟動類GatewayApplication中,加入跨域配置代碼如下

<code>@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);
}
複製代碼/<code>

三、微服務網關過濾器

我們可以通過網關過濾器,實現一些邏輯的處理,比如ip黑白名單攔截、特定地址的攔截等。下面的代碼中做了兩個過濾器,並且設定的先後順序。

(1)在網關微服務中創建IpFilter,無需配置其他,註冊到Spring容器即可生效

<code>@Component
public class IpFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        //TODO 設置ip白名單
        System.out.println("ip:"+remoteAddress.getHostName());
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return 1;
    }
}
複製代碼/<code>

(2)在網關微服務中創建UrlFilter,無需配置其他,註冊到Spring容器即可生效

<code>@Component
public class UrlFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().getPath();
        //TODO 攔截特定URL地址
        System.out.println("url:"+url);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 2;
    }
}
複製代碼/<code>

四、網關限流每秒10萬請求

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

4.1 限流實現思路分析

看圖就完了,非常簡單!

微服務網關Spring Cloud Gateway全搞定

4.2 令牌桶算法 介紹

令牌桶算法是比較常見的限流算法之一,大概描述如下:

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

如下圖:

微服務網關Spring Cloud Gateway全搞定

這個算法的實現,有很多技術,Guava(讀音: 瓜哇)是其中之一,redis客戶端也有其實現。

4.3 網關限流代碼實現

需求:每個ip地址1秒內只能發送10萬請求,多出來的請求返回429錯誤。

代碼實現:

(1)spring cloud gateway 默認使用redis的RateLimter限流算法來實現。所以我們要使用首先需要引入redis的依賴

<code> 

    org.springframework.boot
    spring-boot-starter-data-redis-reactive
    2.1.3.RELEASE

複製代碼/<code>

(2)定義KeyResolver

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

<code>    //定義一個KeyResolver
    @Bean
    public KeyResolver ipKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
            }
        };
    }
複製代碼/<code>

(3)修改application.yml中配置項,指定限制流量的配置以及REDIS的配置,修改後最終配置如下:

<code>spring:
  application:
    name: apigateway
  cloud:
    gateway:
      routes:
      - id: open
        uri: lb://open
        predicates:
        - Path=/open/**
        filters:
        - StripPrefix= 1
        - name: RequestRateLimiter #請求數限流 名字不能隨便寫 
          args:
            key-resolver: "#{@ipKeyResolver}"
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 1
      - id: system
        uri: lb://system
        predicates:
        - Path=/system/**
        filters:
        - StripPrefix= 1
  # 配置Redis 127.0.0.1可以省略配置
  redis:
    host: 101.57.2.128
    port: 6379
server:
  port: 9999
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:100/eureka
  instance:
    prefer-ip-address: true
複製代碼/<code>

解釋:

  • burstCapacity:令牌桶總容量。
  • replenishRate:令牌桶每秒填充平均速率。
  • key-resolver:用於限流的鍵的解析器的 Bean 對象的名字。它使用 SpEL 表達式根據#{@beanName}從 Spring 容器中獲取 Bean 對象。

通過在replenishRate和中設置相同的值來實現穩定的速率burstCapacity。設置burstCapacity高於時,可以允許臨時突發replenishRate。在這種情況下,需要在突發之間允許速率限制器一段時間(根據replenishRate),因為2次連續突發將導致請求被丟棄(HTTP 429 - Too Many Requests)

key-resolver: "#{@userKeyResolver}" 用於通過SPEL表達式來指定使用哪一個KeyResolver.

如上配置:

表示 一秒內,允許 一個請求通過,令牌桶的填充速率也是一秒鐘添加一個令牌。

最大突發狀況 也只允許 一秒內有一次請求,可以根據業務來調整 。

(4)測試時需要注意服務啟動順序,這裡需要依賴於Redis,所以首先要啟動redis

  • 啟動redis
  • 啟動註冊中心
  • 啟動商品微服務
  • 啟動gateway網關
  • 打開瀏覽器 http://localhost:9999/open
  • 快速刷新,當1秒內發送超過10萬次請求,就會返回429錯誤。

那麼問題來了:怎麼發送10萬次請求呢? 知道的同學,請在留言區評論。不知道下次我寫篇壓測來個百萬QPS!


分享到:


相關文章: