微服務網關 Spring Cloud Gateway

什么是網關

假設你現在要做一個電商應用,前端是移動端的APP,后端是各種微服務。那你可能某個頁面需要調用多個服務的數據來展示。如果沒有網關,你的系統(tǒng)看起來就是這個樣子的:


圖片.png

而如果加上了網關,你的系統(tǒng)就會變成這個樣子:


圖片.png

Spring Cloud Gateway

Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發(fā)的網關,它旨在為微服務架構提供一種簡單有效的統(tǒng)一的 API 路由管理方式。

Spring Cloud Gateway 作為 Spring Cloud 生態(tài)系統(tǒng)中的網關,目標是替代 Netflix Zuul,其不僅提供統(tǒng)一的路由方式,并且基于 Filter 鏈的方式提供了網關基本的功能,例如:安全,監(jiān)控/指標,和限流。

相關概念
  • Route(路由):這是網關的基本構建塊。它由一個 ID,一個目標 URI,一組斷言和一組過濾器定義。如果斷言為真,則路由匹配。
  • Predicate(斷言):這是一個 Java 8 的 Predicate。輸入類型是一個 ServerWebExchange。我們可以使用它來匹配來自 HTTP 請求的任何內容,例如 headers 或參數。
  • Filter(過濾器):這是org.springframework.cloud.gateway.filter.GatewayFilter的實例,我們可以使用它修改請求和響應。
工作流程
gateway.png

(PS:看到這張圖是不是很熟悉,沒錯,很像SpringMVC的請求處理過程)

  • 請求發(fā)送到網關,DispatcherHandler是HTTP請求的中央分發(fā)器,接管請求并將請求匹配到相應的 HandlerMapping。

  • 請求與處理器之間有一個映射關系,網關將會對請求進行路由,handler 此處會匹配到 RoutePredicateHandlerMapping,匹配請求對應的 Route。

  • 隨后到達網關的 web 處理器,該 WebHandler 代理了一系列網關過濾器和全局過濾器的實例,如對請求或者響應的 Header 處理(增加或者移除某個 Header)。

  • 最后,轉發(fā)到具體的代理服務。

簡而言之:


圖片.png

客戶端向 Spring Cloud Gateway 發(fā)出請求。如果 Gateway Handler Mapping 中找到與請求相匹配的路由,將其發(fā)送到 Gateway Web Handler。Handler 再通過指定的過濾器鏈來將請求發(fā)送到我們實際的服務執(zhí)行業(yè)務邏輯,然后返回。 過濾器之間用虛線分開是因為過濾器可能會在發(fā)送代理請求之前(“pre”)或之后(“post”)執(zhí)行業(yè)務邏輯。

快速開始

1.新建一個項目gatewayTest,在項目中添加3個moduleeureka,producer,gateway

項目結構


圖片.png
2.rureka

新建module

step1.png

step2.png

step3.png

step4.png

step5.png

添加eureka依賴

 <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
 </dependency>

完整pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.eureka</groupId>
    <artifactId>eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>eureka</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.gateway.test</groupId>
        <artifactId>gatewayTest</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>..</relativePath> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置文件

spring:
  application:
    name: eureka

server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:8761/eureka/

啟動類

package com.example.eureka.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

啟動程序,訪問http://localhost:8761/

圖片.png

現在還沒有服務進行注冊

3.producer

新建producer的module,同創(chuàng)建rureka,不同處如下圖,其他都一樣。


圖片.png

完整pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.producer</groupId>
    <artifactId>producer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>producer</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.gateway.test</groupId>
        <artifactId>gatewayTest</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>..</relativePath> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置文件

spring:
  application:
    name: producer
server:
  port: 8081

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

啟動類

package com.example.producer.producer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class ProducerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class, args);
    }

}

新建2個類控制器

圖片.png

HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("say")
    public String say() {
        return "Hello Every Buddy";
    }
}

GoodByeController

@RestController
@RequestMapping("/goodbye")
public class GoodByeController {

    @RequestMapping("say")
    public String say() {
        return "Bye Bye";
    }
}

啟動程序,訪問http://localhost:8761/

圖片.png

4.gateway

創(chuàng)建過程同eureka
完整pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.gateway</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.gateway.test</groupId>
        <artifactId>gatewayTest</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>..</relativePath> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置文件

test:
  uri: lb://producer

spring:
  application:
    name: gateway
#  cloud:
#    gateway:
#      routes:
#        - id: route_producer_hello
#          uri: ${test.uri} # uri以lb://開頭(lb代表從注冊中心獲取服務),后面接的就是你需要轉發(fā)到的服務名稱
#          predicates:
#            - Path=/api-hello/**
#          filters:
#            - StripPrefix=1 # 表示在轉發(fā)時去掉api
#
#        - id: route_producer_goodbye
#          uri: ${test.uri}
#          predicates:
#            - Path=/api-goodbye/**
#          filters:
#            - StripPrefix=1
#            - name: Hystrix
#              args:
#                name: myfallbackcmd
#                fallbackUri: forward:/user/fallback


server:
  port: 8080

logging:
  level:
    org.springframework.cloud.gateway: TRACE
    org.springframework.http.server.reactive: DEBUG
    org.springframework.web.reactive: DEBUG
    reactor.ipc.netty: DEBUG
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    enabled: true # 是否啟用注冊服務 默認為true, false是不啟用
  instance:
    prefer-ip-address: true

啟動類

package com.example.gateway.gateway;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class GatewayApplication {

    @Value("${test.uri}")
    private String uri;

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder){
        return builder.routes()
                .route(r ->r.path("/hello/**").uri(uri))
                .route(r ->r.path("/goodbye/**").uri(uri)).build();
    }

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

}

啟動程序,訪問http://localhost:8761/

圖片.png

5.測試

服務都已經注冊到reureka,我們定義了hello和goodbye開頭的請求都會轉發(fā)到lb://producer服務,我們定義gateway的端口是8080,producer的端口是8081
直接請求producer服務
http://localhost:8081/hello/say

圖片.png

http://localhost:8081/goodbye/say
圖片.png

通過網關請求
http://localhost:8080/hello/say

hello.png

http://localhost:8080/goodbye/say
goodbye.png

網關本身的負載均衡

那所有微服務就只有一個網關,萬一并發(fā)量上去了,網關承受不住怎么辦?
Spring Cloud Gateway底層是Netty的,它本身就能承受比較大的并發(fā)。如果還是承受不了并發(fā)量,那可以注冊多個Gateway實例,然后在前面弄一個Nginx或者F5等負載均衡器。大概圖是這樣:


圖片.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容