1. 程式人生 > >SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway

SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway

error att ESS cto redirect rac 進行 void 收集

一、前言

1、SpringCloudGateway是SpringCloud新推出的網關框架,比較於上一代Zuul,功能和性能有很大的提升。Zuul1.x采用的是阻塞多線程方式,也就是一個線程處理一個連接請求,高並發情況下性能較差,即使是Zuul2.x雖然做到了非阻塞,但是面對連續跳票,看起來Zuul要被拋棄了。取而代之的是SpringCloudGateway,SpringCloudGateway是基於Webflux,是一個非阻塞異步的框架,性能上有很大提升,而且包含了Zuul的所有功能,可以從Zuul無縫切換到SpringCloudGateway

2、SpringCloud環境版本:Greenwich.RELEASE

3、SpringBoot環境版本:2.1.3.RELEASE

二、環境搭建

1、在父工程下新建一個網關模塊

2、引入SpringCloudGateway需要的POM,記得引入actuator組件,否則服務發現中心會認為服務不在線,導致網關無法路由到服務,並且加入熔斷組件Hystrix

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 健康檢查 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

3、主函數很簡單,加入必要的註解,比如服務發現等

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayCarfacApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(GatewayCarfacApplication.class, args);
    }
}

4、進行SpringCloudGateway的配置,配置可以使用Java代碼進行配置或者yml配置,這裏使用yml進行配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 設置可以路由到其他服務
      routes: # 可以配置多個路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id>
          predicates:
            - Path=/media/** # 路由規則
          filters:
            - StripPrefix=1 # 不填則無法路由到其他服務
            - AddRequestHeader=X-Request-Foo, Bar
            - name: Hystrix # 添加熔斷
              args:
                name: fallbackcmd
                fallbackUri: forward:/test/fallback # 熔斷跳轉地址

# 熔斷超時時間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000

5、上面我們配置了熔斷的配置,一旦發生熔斷就會跳轉到/test/fallback這個地址,下面我們實現一下這個接口,這裏簡單的返回了error,我們可以自定義處理熔斷的邏輯

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping(value = "/fallback", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<String> fallback()
    {
        return new ResponseEntity<>("error.", HttpStatus.OK);
    }
}

6、網關配置好後,再啟動一個其他的服務模塊,記得添加引入actuator,否則無法被路由到

<!-- Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 健康檢查 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

7、編寫一個HTTP接口,來測試網關的調用,為了檢測熔斷的效果,這裏通過參數來控制接口的響應時間

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @Value("${server.port}")
    private String port;

    @RequestMapping(value = "/get/{time}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<String> get(@PathVariable("time") long time)
    {
        try
        {
            Thread.sleep(time);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return new ResponseEntity<>(port + " get ok.", HttpStatus.OK);
    }
}

8、在同一註冊中心下啟動網關和服務,根據我們配置的路由規則/media/**,我們可以這樣調用http://127.0.0.1:8501/media/get/100,可以看到正確的路由到了服務

技術分享圖片

9、我們將響應時間改成5秒http://127.0.0.1:8501/media/get/5000,超高熔斷的檢測時間,可以發現接口熔斷,並且跳轉到了指定的鏈接

技術分享圖片

三、高級特性---斷言

1、以上我們實現了SpringCloudGateway的基本使用辦法,可以應付大部分應用場景了,我們同時可以細粒度的去改造路由,就使用到了斷言Predict,如果不滿足我們設置的斷言條件,則無法被路由

2、設置斷言,我們只需要在predicates下面加入配置即可,要註意如果設置了多個斷言,則請求必須滿足所有斷言才可以被正確路由到

spring:
  cloud:
    gateway:
      routes: # 可以配置多個路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id>
          predicates:
            # 加入斷言

3、常用的一些斷言,還有很多其他斷言就不一一列舉了,當然最常用的可能是Path斷言,我們需要通過路徑來指定路由

(1)- Header=<key>, <value>(必須有指定的HTTP Header才能路由)

(2)- Cookie=<key>, <value>(必須有指定的Cookie才能路由)

(3)- Host=aaa.bbb.com(請求域名必須是aaa.bbb.com才能路由)

(4)- Method=GET(請求方式必須是Get請求才能路由)

四、高級特性---過濾器

1、通過設置網關的過濾器,我們可以在用戶訪問的入口增加一些處理,比如鑒權、接口監控等,下面介紹一些常用的過濾器

2、常用的過濾器

(1)- AddRequestHeader=<key>, <value>(增加自定義HTTP請求頭)

(2)- SetStatus=401(設置響應的HTTP錯誤碼)

(3)- RedirectTo=302, http://acme.org(重定向到指定鏈接)

3、自定義過濾器,可以通過代碼定制更加靈活的過濾器,下面實現一個簡單的接口耗時統計的過濾器,思路:設置兩個過濾器,一個前置過濾器用來收集接口開始調用的時間,一個後置過濾器來將結束時間減去開始時間得到接口的耗時時間,並打印出來

(1)前置過濾器工廠,將開始時間寫到attributes裏面

public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put("requestTime", System.currentTimeMillis());
            return chain.filter(exchange);
        };
    }

    public static class Config {

    }

}

(2)後置過濾器工廠,獲取attribute裏面的開始時間,並用當前時間減去,得到耗時時間,打印

public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config>
{
    public PostGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                Long startTime = exchange.getAttribute("requestTime");
                long time = System.currentTimeMillis() - startTime;
                System.out.println("接口耗時時間(ms):"+time);
            }));
        };
    }

    public static class Config {
        
    }

}

(3)接下來將兩個過濾器註入到Spring裏面

@Configuration
public class FilterConfig
{
    @Bean
    public PreGatewayFilterFactory preGatewayFilterFactory() {
        return new PreGatewayFilterFactory();
    }

    @Bean
    public PostGatewayFilterFactory postGatewayFilterFactory() {
        return new PostGatewayFilterFactory();
    }
}

(4)前往application.yml進行過濾器的配置,在filter屬性下配置上我們的自定義過濾器,根據框架的約定引用過濾器:比如我們的過濾器名稱是PreGatewayFilterFactory,那我們引用的名稱就是去掉GatewayFilterFactory這個後綴,也就是Pre,具體配置如下

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 設置可以路由到其他服務
      routes: # 可以配置多個路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id>
          predicates:
            - Path=/media/** # 路由規則
          filters:
            - StripPrefix=1 # 不填則無法路由到其他服務
            - Pre # 自定義過濾器
            - Post # 自定義過濾器

(5)接下來我們調用接口,可以看到耗時時間打在公屏上

五、網關限流

1、我們可以在網關層面做限流的功能,防止高並發時把服務器搞崩,或者應對一些網絡攻擊等情況

2、SpringCloudGateway為我們提供了一個很方便使用的令牌桶限流,思路:我們設置一個固定大小的令牌桶,如果令牌桶不滿,則根據一定的頻率向桶裏放入令牌,每當有客戶端的請求發來,會先從令牌桶裏面取令牌,取到了則繼續執行,取不到請求則會被拒絕,這樣就達到了限流的作用,我們可以靈活地調整令牌補充速率和令牌桶大小,來細粒度的控制限流,我們使用redis來存儲令牌,下面是一個簡單的demo

(1)引入redis依賴

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

(2)接下來自定義限流的維度,我們可以從域名、uri等維度進行限流

public class HostAddrKeyResolver implements KeyResolver
{
    @Override
    public Mono<String> resolve(ServerWebExchange exchange)
    {
        // 域名維度限流
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
        // uri維度限流
        // return Mono.just(exchange.getRequest().getURI().getPath());
    }
}

(3)將解析器註入到Spring

@Configuration
public class TokenLimitConfig
{
    @Bean
    public HostAddrKeyResolver hostAddrKeyResolver() {
        return new HostAddrKeyResolver();
    }
}

(4)接下來配置限流器的配置,將我們編寫的解析器引用,並且配置令牌桶的屬性,這裏我配置的是,每秒補充一個令牌,令牌桶的大小為3,最後配置redis的參數,用起來非常簡單

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 設置可以路由到其他服務
      routes: # 可以配置多個路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳轉到具體的地址,如果要跳轉到其他服務,則填寫lb://<服務id>
          predicates:
            - Path=/media/** # 路由規則
          filters:
            - StripPrefix=1 # 不填則無法路由到其他服務
            - name: RequestRateLimiter # 配置限流器
              args:
                key-resolver: #{@hostAddrKeyResolver} # 自定義限流過濾器
                redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
                redis-rate-limiter.burstCapacity: 3 # 令牌桶總容量
  redis:
    host: 127.0.0.1
    port: 4000
    password: 123456

(5)配置好後,使用JMeter來壓測一下接口,可以發現某些請求返回429錯誤碼,被限流

技術分享圖片

SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway