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

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

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的實例,我們可以使用它修改請求和響應。
工作流程

(PS:看到這張圖是不是很熟悉,沒錯,很像SpringMVC的請求處理過程)
請求發(fā)送到網關,DispatcherHandler是HTTP請求的中央分發(fā)器,接管請求并將請求匹配到相應的 HandlerMapping。
請求與處理器之間有一個映射關系,網關將會對請求進行路由,handler 此處會匹配到 RoutePredicateHandlerMapping,匹配請求對應的 Route。
隨后到達網關的 web 處理器,該 WebHandler 代理了一系列網關過濾器和全局過濾器的實例,如對請求或者響應的 Header 處理(增加或者移除某個 Header)。
最后,轉發(fā)到具體的代理服務。
簡而言之:

客戶端向 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
項目結構

2.rureka
新建module





添加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/

現在還沒有服務進行注冊
3.producer
新建producer的module,同創(chuàng)建rureka,不同處如下圖,其他都一樣。

完整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個類控制器

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/

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/

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

http://localhost:8081/goodbye/say

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

http://localhost:8080/goodbye/say

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