文章目錄
1. API網關
1.1 什麼是API網關
API Gateway 是隨着微服務(Microservice)這個概念一起興起的一種架構模式,它用於解決微服務過於分散,沒有一個統一的出入口進行流量管理的問題。
API網關可以看做系統與外界聯通的入口,我們可以在網關進行處理一些非業務邏輯的邏輯,比如權限驗證,監控,緩存,請求路由等等。
1.2 爲什麼需要API網關
- RPC協議轉成HTTP。
由於在內部開發中我們都是以RPC協議(thrift or dubbo)去做開發,暴露給內部服務, 當外部服務需要使用這個接口的時候往往需要將RPC協議轉換成HTTP協議。 - 請求路由
在我們的系統中由於同一個接口新老兩套系統都在使用,我們需要根據請求上下文將請求路由到對應的接口。 - 統一鑑權
對於鑑權操作不涉及到業務邏輯,那麼可以在網關層進行處理,不用下層到業務邏輯。 - 統一監控
由於網關是外部服務的入口,所以我們可以在這裏監控我們想要的數據,比如入參出參,鏈路時間。 - 流量控制,熔斷降級
對於流量控制,熔斷降級非業務邏輯可以統一放到網關層。
有很多業務都會自己去實現一層網關層,用來接入自己的服務,但是對於整個公司來說這還不夠。
1.3 統一API網關
統一的API網關不僅有API網關的所有的特點,還有下面幾個好處:
- 統一技術組件升級
在公司中如果有某個技術組件需要升級,那麼是需要和每個業務線溝通,通常幾個月都搞不定。舉個例子如果對於入口的安全鑑權有重大安全隱患需要升級,如果速度還是這麼慢肯定是不行,那麼有了統一的網關升級是很快的。 - 統一服務接入
對於某個服務的接入也比較困難,比如公司已經研發出了比較穩定的服務組件,正在公司大力推廣,這個週期肯定也特別漫長,由於有了統一網關,那麼只需要統一網關統一接入。 - 節約資源
不同業務不同部門如果按照我們上面的做法應該會都自己搞一個網關層,用來做這個事,可以想象如果一個公司有100個這種業務,每個業務配備4臺機器,那麼就需要 400臺機器。並且每個業務的開發RD都需要去開發這個網關層,去隨時去維護,增加人力。如果有了統一網關層,那麼也許只需要50臺機器就可以做這100個業務的網關 層的事,並且業務RD不需要隨時關注開發,上線的步驟。
2. 統一網關的設計
2.1 異步化請求
對於我們自己實現的網關層,由於只有我們自己使用,對於吞吐量的要求並不高,所以,我們一般同步請求調用即可。
對於我們統一的網關層,如何用少量的機器接入更多的服務,這就需要我們採用異步,用來提高更多的吞吐量。對於異步化一般有下面兩種策略:
- Tomcat/Jetty+NIO+servlet3
這種策略使用的比較普遍,京東,有贊,Zuul,都選取的是這個策略,這種策略比較適合HTTP。在Servlet3中可以開啓異步。 - Netty+NIO
Netty爲高併發而生,目前唯品會的網關使用這個策略,在唯品會的技術文章中在相同的情況下Netty是每秒30w+的吞吐量,Tomcat是13w+,可以看出是有一定的差距的, 但是Netty需要自己處理HTTP協議,這一塊比較麻煩。
對於網關是HTTP請求場景比較多的情況可以採用Servlet,畢竟有更加成熟的處理 HTTP協議。如果更加重視吞吐量那麼可以採用Netty。
2.1.1 全鏈路異步
對於來的請求我們已經使用異步了,爲了達到全鏈路異步,所以我們需要對去的請求 也進行異步處理,對於去的請求我們可以利用我們rpc的異步支持進行異步請求,所以基本可以達到下圖:
由在web容器中開啓servlet異步,然後進入到網關的業務線程池中進行業務處理,然後進行rpc的異步調用並註冊需要回調的業務,最後在回調線程池中進行回調處理。
2.2 鏈式處理
在設計模式中有一個模式叫責任鏈模式,他的作用是避免請求發送者與接收者耦合在 一起,讓多個對象都有可能接收請求,將這些對象連接成一條鏈,並且沿着這條鏈傳遞請求,直到有對象處理它爲止。通過這種模式將請求的發送者和請求的處理者解耦了。在我們的各個框架中對此模式都有實現,比如servlet裏面的filter,springmvc裏面 的Interceptor。
在Netflix Zuul中也應用了這種模式,如下圖所示:
這種模式在網關的設計中我們可以借鑑到自己的網關設計:
- preFilters:前置過濾器,用來處理一些公共的業務,比如統一鑑權,統一限流,熔斷降級,緩存處理等,並且提供業務方擴展。
- routingFilters:用來處理一些泛化調用,主要是做協議的轉換,請求的路由工作。
- postFilters:後置過濾器,主要用來做結果的處理,日誌打點,記錄時間等等。
- errorFilters:錯誤過濾器,用來處理調用異常的情況。
這種設計在有讚的網關也有應用。
2.3 業務隔離
上面在全鏈路異步的情況下不同業務之間的影響很小,但是如果在提供的自定義 FiIlter中進行了某些同步調用,一旦超時頻繁那麼就會對其他業務產生影響。所以我們需要採用隔離技術,降低業務之間的互相影響。
2.3.1 信號量隔離
信號量隔離只是限制了總的併發數,服務還是主線程進行同步調用。這個隔離如果遠程調用超時依然會影響主線程,從而會影響其他業務。因此,如果只是想限制某個服務的總併發調用量或者調用的服務不涉及遠程調用的話,可以使用輕量級的信號量來實現。有讚的網關由於沒有自定義filter所以選取的是信號量隔離。
2.3.2 線程池隔離
最簡單的就是不同業務之間通過不同的線程池進行隔離,就算業務接口出現了問題由於線程池已經進行了隔離那麼也不會影響其他業務。在京東的網關實現之中就是採用的線程池隔離,比較重要的業務比如商品或者訂單都是單獨的通過線程池去處理。但是由於是統一網關平臺,如果業務線衆多,大家都覺得自己的業務比較重要需要單獨的線程池隔離,如果使用的是Java語言開發的話那麼,在Java中線程是比較重的資源比較受限,如果需要隔離的線程池過多不是很適用。如果使用一些其他語言比如 Golang進行開發網關的話,線程是比較輕的資源,所以比較適合使用線程池隔離。
2.3.3 集羣隔離
如果有某些業務就需要使用隔離但是統一網關又沒有線程池隔離那麼應該怎麼辦呢?那麼可以使用集羣隔離,如果你的某些業務真的很重要那麼可以爲這一系列業務單獨申請一個集羣或者多個集羣,通過機器之間進行隔離。
2.4 請求限流
流量控制可以採用很多開源的實現,比如阿里最近開源的Sentinel和比較成熟的 Hystrix。
一般限流分爲集羣限流和單機限流:
- 利用統一存儲保存當前流量的情況,一般可以採用Redis,這個一般會有一些性能損耗。
- 單機限流:限流每臺機器我們可以直接利用Guava的令牌桶去做,由於沒有遠程調用性能消耗較小。
2.5 熔斷降級
這一塊也可以參照開源的實現Sentinel和Hystrix,這裏不是重點就不多提了。
2.6 泛化調用
泛化調用指的是一些通信協議的轉換,比如將HTTP轉換成Thrift。在一些開源的網關 中比如Zuul是沒有實現的,因爲各個公司的內部服務通信協議都不同。比如在唯品會 中支持HTTP1,HTTP2,以及二進制的協議,然後轉化成內部的協議,淘寶的支持 HTTPS,HTTP1,HTTP2這些協議都可以轉換成,HTTP,HSF,Dubbo等協議。
如何去實現泛化調用呢?由於協議很難自動轉換,那麼其實每個協議對應的接口需要提供一種映射。簡單來說就是把兩個協議都能轉換成共同語言,從而互相轉換。
一般來說共同語言有三種方式指定:
- json:json數據格式比較簡單,解析速度快,較輕量級。在Dubbo的生態中有一個HTTP轉Dubbo的項目是用JsonRpc做的,將HTTP轉化成JsonRpc再轉化成Dubbo。
比如可以將一個 www.baidu.com/id = 1 GET 可以映射爲json:{ “method”: "getBaidu" "param" : { "id":1 } }
- xml:ml數據比較重,解析比較困難,這裏不過多討論。
- 自定義描述語言:一般來說這個成本比較高需要自己定義語言來進行描述並進行解析,
但是其擴展性,自定義個性化性都是最高。比如:spring自定義了一套自己的SPEL表達式語言
對於泛化調用如果要自己設計的話JSON基本可以滿足,如果對於個性化的需要特別多的話倒是可以自己定義一套語言。
2.7 管理平臺
上面介紹的都是如何實現一個網關的技術關鍵。這裏需要介紹網關的一個業務關鍵。 有了網關之後,需要一個管理平臺如何去對我們上面所描述的技術關鍵進行配置,包括 但不限於下面這些配置:
- 限流
- 熔斷
- 緩存
- 日誌
- 自定義filter
- 泛化調用
3. 總結
最後一個合理的標準網關應該按照如下去實現:
4. Spring Cloud Zuul
Spring Cloud Zuul 是Spring Cloud Netflix 子項目的核心組件之一,可以作爲微服務架構中的API網關使用,支持動態路由與過濾功能。
4.1 Zuul簡介
API網關爲微服務架構中的服務提供了統一的訪問入口,客戶端通過API網關訪問相關服務。API網關的定義類似於設計模式中的門面模式,它相當於整個微服務架構中的門面,所有客戶端的訪問都通過它來進行路由及過濾。它實現了請求路由、負載均衡、校驗過濾、服務容錯、服務聚合等功能。
4.2 創建一個zuul-proxy模塊
這裏我們創建一個zuul-proxy模塊來演示zuul的常用功能。
在pom.xml中添加相關依賴
<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‐zuul</artifactId>
</dependency>
在application.yml中進行配置
server:
port: 8801
spring:
application:
name: zuul-proxy
eureka:
instance:
hostname: replica1
client:
serviceUrl:
defaultZone: http://replica2:8003/eureka/ #註冊到另一個Eureka註冊中心
fetch‐registry: true
register‐with‐eureka: true
在啓動類上添加@EnableZuulProxy註解來啓用Zuul的API網關功能
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.cloud.client.discovery.EnableDiscoveryClient;
importorg.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulProxyApplication{
public static void main(String[] args) {
SpringApplication.run(ZuulProxyApplication.class, args);
}
}
4.3常用功能
啓動相關服務
我們通過啓動eureka-server,兩個user-service,feign-service和zuul-proxy來演示Zuul 的常用功能,啓動後註冊中心顯示如下。
配置路由規則
我們可以通過修改application.yml中的配置來配置路由規則,這裏我們將匹配/userService/**
的請求路由到user-service服務上去,匹配/feignService/**
的 請求路由到feign-service上去。
zuul:
routes: #給服務配置路由
user‐service:
path: /userService/**
feign‐service:
path: /feignService/**
訪問http://localhost:8801/userService/user/1可以發現請求路由到了user- service上了;
訪問http://localhost:8801/feignService/user/1可以發現請求路由到了feign- service上了。
默認路由規則
Zuul和Eureka結合使用,可以實現路由的自動配置,自動配置的路由以服務名稱爲匹配路徑,相當於如下配置:
zuul:
routes: #給服務配置路由
user‐service:
path: /user‐service/**
feign‐service:
path: /feign‐service/**
訪問http://localhost:8801/user-service/user/1
同樣可以路由到了user- service上了;
訪問http://localhost:8801/feign-service/user/1
同樣可以路由到了feign- service上了。
如果不想使用默認的路由規則,可以添加以下配置來忽略默認路由配置:
zuul:
ignored‐services: user‐service,feign‐service #關閉默認路由配置
負載均衡功能
多次調用http://localhost:8801/user-service/user/1
進行測試,可以發現運行在8201和 8202的user-service服務交替打印如下信息。
配置訪問前綴
我們可以通過以下配置來給網關路徑添加前綴,此處添加了/proxy前綴,這樣我們需要訪問http://localhost:8801/proxy/user-service/user/1
才能訪問到user-service中的接口。
zuul:
prefix: /proxy #給網關路由添加前綴
Header過濾及重定向添加Host
Zuul在請求路由時,默認會過濾掉一些敏感的頭信息,以下配置可以防止路由時的Cookie及Authorization的丟失:
zuul:
sensitive‐headers: Cookie,Set‐Cookie,Authorization #配置過濾敏感的請求頭信息,設置爲空就不會過濾
Zuul在請求路由時,不會設置最初的host頭信息,以下配置可以解決:
zuul:
add‐host‐header: true #設置爲true重定向是會添加host請求頭
查看路由信息
我們可以通過SpringBoot Actuator來查看Zuul中的路由信息。
在pom.xml中添加相關依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐actuator</artifactId>
</dependency>
修改application.yaml配置文件,開啓查看路由的端點:
management:
endpoints:
web:
exposure:
include: 'routes'
通過訪問http://localhost:8801/actuator/routes
查看簡單路由信息:
通過訪問http://localhost:8801/actuator/routes/details
查看詳細路由信息:
4.4 過濾器
路由與過濾是Zuul的兩大核心功能,路由功能負責將外部請求轉發到具體的服務實例上去,是實現統一訪問入口的基礎,過濾功能負責對請求過程進行額外的處理,是請求校驗過濾及服務聚合的基礎。
過濾器類型
Zuul中有以下幾種典型的過濾器類型。
- pre:在請求被路由到目標服務前執行,比如權限校驗、打印日誌等功能;
- routing:在請求被路由到目標服務時執行,這是使用Apache HttpClient或 Netflix Ribbon構建和發送原始HTTP請求的地方;
- post:在請求被路由到目標服務後執行,比如給目標服務的響應添加頭信息,收 集統計數據等功能;
- error:請求在其他階段發生錯誤時執行。
過濾器的生命週期
下圖描述了一個HTTP請求到達API網關後,如何在各種不同類型的過濾器中流轉的過程。
自定義過濾器
接下來我們自定義一個過濾器來演示下過濾器的作用。
添加PreLogFilter類繼承ZuulFilter
這是一個前置過濾器,用於在請求路由到目標服務前打印請求日誌。
@Component
public class PreLogFilterextendsZuulFilter{
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/**
* 過濾器類型,有pre、routing、post、error四種。
*/
@Override
public String filterType() {
return "pre";
}
/**
* 過濾器執行順序,數值越小優先級越高。
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否進行過濾,返回true會執行過濾。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 自定義的過濾器邏輯,當shouldFilter()返回true時會執行。
*/
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String host = request.getRemoteHost();
String method = request.getMethod();
String uri = request.getRequestURI();
LOGGER.info("Remote host:{},method:{},uri:{}", host, method, uri);
return null;
}
}
過濾器功能演示
添加過濾器後,我們訪問http://localhost:8801/user-service/user/1
測試下,會打印如下日誌。
核心過濾器
禁用過濾器
我們可以對過濾器進行禁用的配置,配置格式如下:
zuul:
filterClassName:
filter:
disable: true
以下是禁用PreLogFilter的示例配置:
zuul:
PreLogFilter:
pre:
disable: true
4.5 Ribbon和Hystrix的支持
由於Zuul自動集成了Ribbon和Hystrix,所以Zuul天生就有負載均衡和服務容錯能力, 我們可以通過Ribbon和Hystrix的配置來配置Zuul中的相應功能。
可以使用Hystrix的配置來設置路由轉發時HystrixCommand的執行超時時間:
hystrix:
command: #用於控制HystrixCommand的行爲
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #配置HystrixCommand執行的超時時間,執行超過該時間會進行服務降級處理
可以使用Ribbon的配置來設置路由轉發時請求連接及處理的超時時間:
ribbon:#全局配置
ConnectTimeout: 1000 #服務請求連接超時時間(毫秒)
ReadTimeout: 3000 #服務請求處理超時時間(毫秒)
常用配置
zuul:
routes: #給服務配置路由
user‐service:
path: /userService/**
feign‐service:
path: /feignService/**
ignored‐services: user‐service,feign‐service #關閉默認路由配置
prefix: /proxy #給網關路由添加前綴
sensitive‐headers: Cookie,Set‐Cookie,Authorization #配置過濾敏感的請求頭信息,設置爲空就不會過濾
add‐host‐header: true #設置爲true重定向是會添加host請求頭
retryable: true # 關閉重試機制
PreLogFilter:
pre:
disable: false #控制是否啓用過濾器
5. Spring Cloud Gateway:新一代API網關服務
Spring Cloud Gateway 爲 SpringBoot 應用提供了API網關支持,具有強大的智能路由與過濾器功能,下面將對其用法進行詳細介紹。
5.1 Gateway 簡介
Gateway是在Spring生態系統之上構建的API網關服務,基於Spring 5,Spring Boot 2和 Project Reactor等技術。Gateway旨在提供一種簡單而有效的方式來對API進行路由,以及 提供一些強大的過濾器功能, 例如:熔斷、限流、重試等。
Spring Cloud Gateway 具有如下特性:
- 基於Spring Framework 5, Project Reactor 和 Spring Boot 2.0 進行構建;
- 動態路由:能夠匹配任何請求屬性;
- 可以對路由指定 Predicate(斷言)和 Filter(過濾器);
- 集成Hystrix的斷路器功能;
- 集成 Spring Cloud 服務發現功能;
- 易於編寫的 Predicate(斷言)和 Filter(過濾器);
- 請求限流功能;
- 支持路徑重寫。
5.2 相關概念
- Route(路由):路由是構建網關的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,如果斷言爲true則匹配該路由;
- Predicate(斷言):指的是Java 8 的 Function Predicate。 輸入類型是 Spring框架中的ServerWebExchange。 這使開發人員可以匹配HTTP請求中的所有內容,例如請求頭或請求參數。如果請求與斷言相匹配,則進行路由;
- Filter(過濾器):指的是Spring框架中GatewayFilter的實例,使用過濾器, 可以在請求被路由前後對請求進行修改。
5.3 創建 api-gateway模塊
創建一個api-gateway模塊來演示Gateway的常用功能。
在pom.xml中添加相關依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐gateway</artifactId>
</dependency>
兩種不同的配置路由方式
Gateway 提供了兩種不同的方式用於配置路由,一種是通過yml文件來配置,另一種是通過Java Bean來配置,下面我們分別介紹下。
使用yml配置
server:
port: 9201
service‐url:
user‐service: http://localhost:8201
spring:
cloud:
gateway:
routes:
- id: path_route #路由的ID
uri: ${service‐url.user‐service}/user/{id} #匹配後路由地址
predicates: # 斷言,路徑相匹配的進行路由
‐ Path=/user/{id}
啓動eureka-server,user-service和api-gateway服務,並調用該地址測試:
http://localhost:9201/user/1
我們發現該請求被路由到了user-service的該路徑上:
http://localhost:8201/user/1
使用Java Bean配置
添加相關配置類,並配置一個RouteLocator對象:
@Configuration
public class GatewayConfig{
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route2", r ‐> r.path("/user/getByUsername")
.uri("http://localhost:8201/user/getByUsername"))
.build();
}
}
重新啓動api-gateway服務,並調用該地址測試:
http://localhost:9201/user/getByUsername?username=macro
我們發現該請求被路由到了user-service的該路徑上:
http://localhost:8201/user/getByUsername?username=macro
5.4 Route Predicate 的使用
Spring Cloud Gateway將路由匹配作爲Spring WebFlux HandlerMapping基礎架構的一部分。
Spring Cloud Gateway包括許多內置的Route Predicate工廠。 所有這些 Predicate都與HTTP請求的不同屬性匹配。 多個Route Predicate工廠可以進行組合,下面我們來介紹下一些常用的Route Predicate。
注意:Predicate中提到的配置都在application-predicate.yml文件中進行修改,並用該配置啓動api-gateway服務。
After Route Predicate
在指定時間之後的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: after_route
uri: ${service‐url.user‐service}
predicates:
‐ After=2020‐03‐08T16:30:00+08:00[Asia/Shanghai]
Before Route Predicate
在指定時間之前的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: before_route
uri: ${service‐url.user‐service}
predicates:
‐ Before=2020‐03‐09T16:30:00+08:00[Asia/Shanghai]
Between Route Predicate
在指定時間區間內的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: before_route
uri: ${service‐url.user‐service}
predicates:
‐ Between=2020‐03‐08T16:30:00+08:00[Asia/Shanghai], 2020‐03‐ 09T16:30:00+08:00[Asia/Shanghai]
Cookie Route Predicate
帶有指定Cookie的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: cookie_route
uri: ${service‐url.user‐service}
predicates:
‐ Cookie=username,macro
使用curl工具發送帶有cookie爲username=macro的請求可以匹配該路由。
curl http://localhost:9201/user/1 ‐‐ cookie "username=macro"
Header Route Predicate
帶有指定請求頭的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: header_route
uri: ${service‐url.user‐service}
predicates:
‐ Header=X‐Request‐Id, \d+
使用curl工具發送帶有請求頭爲X-Request-Id:123的請求可以匹配該路由。
curl http://localhost:9201/user/1 ‐H "X‐Request‐Id:123"
Host Route Predicate
帶有指定Host的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: host_route
uri: ${service‐url.user‐service}
predicates:
‐ Host= .macrozheng.com
使用curl工具發送帶有請求頭爲Host:www.macrozheng.com的請求可以匹配該路由。
curl http://localhost:9201/user/1 ‐H "Host:www.macrozheng.com"
Method Route Predicate
發送指定方法的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: method_route
uri: ${service‐url.user‐service}
predicates:
‐ Method=GET
使用curl工具發送GET請求可以匹配該路由。
curl http://localhost:9201/user/1
使用curl工具發送POST請求無法匹配該路由。
curl ‐X POST http://localhost:9201/user/1
Path Route Predicate
發送指定路徑的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: path_route
uri: ${service‐url.user‐service}/user/{id}
predicates:
‐ Path=/user/{id}
使用curl工具發送/user/1路徑請求可以匹配該路由。
curl http://localhost:9201/user/1
使用curl工具發送/abc/1路徑請求無法匹配該路由。
curl http://localhost:9201/abc/1
Query Route Predicate
帶指定查詢參數的請求可以匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: query_route
uri: ${service‐url.user‐service}/user/getByUsername
predicates:
‐ Query=username
使用curl工具發送帶username=macro查詢參數的請求可以匹配該路由。
curl http://localhost:9201/user/getByUsername?username=macro
使用curl工具發送帶不帶查詢參數的請求無法匹配該路由。
curl http://localhost:9201/user/getByUsername
RemoteAddr Route Predicate
從指定遠程地址發起的請求可以匹配該路由。
spring:
cloud:
gateway:
routes:
‐ id: remoteaddr_route
uri: ${service‐url.user‐service}
predicates:
‐ RemoteAddr=192.168.1.1/24
使用curl工具從192.168.1.1發起請求可以匹配該路由。
curl http://localhost:9201/user/1
Weight Route Predicate
使用權重來路由相應請求,以下表示有80%的請求會被路由到localhost:8201,20%會被路由到localhost:8202。
spring:
cloud:
gateway:
routes:
‐ id: weight_high
uri: http://localhost:8201
predicates:
‐ Weight=group1, 8
‐ id: weight_low
uri: http://localhost:8202
predicates:
‐ Weight=group1, 2
5.5 Route Filter 的使用
路由過濾器可用於修改進入的HTTP請求和返回的HTTP響應,路由過濾器只能指定路由進行使用。Spring Cloud Gateway 內置了多種路由過濾器,他們都由 GatewayFilter的工廠類來產生,下面我們介紹下常用路由過濾器的用法。
AddRequestParameter GatewayFilter
給請求添加參數的過濾器。
spring:
cloud:
gateway:
routes:
‐ id: add_request_parameter_route
uri: http://localhost:8201
filters:
‐ AddRequestParameter=username, macro
predicates:
‐ Method=GET
以上配置會對GET請求添加username=macro的請求參數,通過curl工具使用以下命令進行測 試。
curl http://localhost:9201/user/getByUsername
相當於發起該請求:
curl http://localhost:8201/user/getByUsername?username=macro
StripPrefix GatewayFilter
對指定數量的路徑前綴進行去除的過濾器。
spring:
cloud:
gateway:
routes:
‐ id: strip_prefix_route
uri: http://localhost:8201
predicates:
‐ Path=/user‐service/**
filters:
‐ StripPrefix=2
以上配置會把以/user-service/開頭的請求的路徑去除兩位,通過curl工具使用以下命令進行測試。
curl http://localhost:9201/user‐service/a/user/1
相當於發起該請求:
curl http://localhost:8201/user/1
PrefixPath GatewayFilter
spring:
cloud:
gateway:
routes:
‐ id: prefix_path_route
uri: http://localhost:8201
predicates:
‐ Method=GET
filters:
‐ PrefixPath=/user
以上配置會對所有GET請求添加/user路徑前綴,通過curl工具使用以下命令進行測試。
curl http://localhost:9201/1
相當於發起該請求:
curl http://localhost:8201/user/1
Hystrix GatewayFilter
Hystrix 過濾器允許你將斷路器功能添加到網關路由中,使你的服務免受級聯故障的影響, 並提供服務降級處理。
要開啓斷路器功能,我們需要在pom.xml中添加Hystrix的相關依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐netflix‐hystrix</artifactId>
</dependency>
然後添加相關服務降級的處理類:
@RestController
public class FallbackController{
@GetMapping("/fallback")
public Object fallback() {
Map<String,Object> result = new HashMap<>();
result.put("data",null);
result.put("message","Get request fallback!");
result.put("code",500);
return result;
}
}
在application-filter.yml中添加相關配置,當路由出錯時會轉發到服務降級處理的控制器上:
spring:
cloud:
gateway:
routes:
‐ id: hystrix_route
uri: http://localhost:8201
predicates:
‐ Method=GET
filters:
‐ name: Hystrix 11 args:
name: fallbackcmd
fallbackUri: forward:/fallback
關閉user-service,調用該地址進行測試:http://localhost:9201/user/1
,發現已經返回了服務降級的處理信息。
RequestRateLimiter GatewayFilter
RequestRateLimiter 過濾器可以用於限流,使用RateLimiter實現來確定是否允許當前請求繼續進行,如果請求太大默認會返回HTTP 429-太多請求狀態。
在pom.xml中添加相關依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐data‐redis‐reactive</artifactId>
</dependency>
添加限流策略的配置類,這裏有兩種策略一種是根據請求參數中的username進行限流,另一種是根據訪問IP進行限流;
@Configuration
public class RedisRateLimiterConfig{
@Bean
KeyResolver userKeyResolver() {
return exchange ‐> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
@Bean
public KeyResolver ipKeyResolver() {
return exchange ‐> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
我們使用Redis來進行限流,所以需要添加Redis和RequestRateLimiter的配 置,這裏對所有的GET請求都進行了按IP來限流的操作;
server:
port: 9201
spring:
redis:
host: localhost
password: 123456
port: 6379
cloud:
gateway:
routes:
‐ id: requestratelimiter_route
uri: http://localhost:8201
filters:
‐ name: RequestRateLimiter
args:
redis‐rate‐limiter.replenishRate: 1 #每秒允許處理的請求數量
redis‐rate‐limiter.burstCapacity: 2 #每秒最大處理的請求數量
key‐resolver: "#{@ipKeyResolver}" #限流策略,對應策略的Bean
predicates:
‐ Method=GET
logging:
level:
org.springframework.cloud.gateway: debug
多次請求該地址:http://localhost:9201/user/1
,會返回狀態碼爲429的錯誤;
Retry GatewayFilter
對路由請求進行重試的過濾器,可以根據路由請求返回的HTTP狀態碼來確定是否進行重試。
修改配置文件:
spring:
cloud:
gateway:
routes:
‐ id: retry_route
uri: http://localhost:8201
predicates:
‐ Method=GET
filters:
‐ name: Retry
args:
retries: 1 #需要進行重試的次數
statuses: BAD_GATEWAY #返回哪個狀態碼需要進行重試,返回狀態碼爲5XX進行重試
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
當調用返回500時會進行重試,訪問測試地址:
http://localhost:9201/user/111
可以發現user-service控制檯報錯2次,說明進行了一次重試。
5.6 結合註冊中心使用
我們上面講到使用Zuul作爲網關結合註冊中心進行使用時,默認情況下Zuul會根據註冊中心註冊的服務列表,以服務名爲路徑創建動態路由,Gateway同樣也實現了該功能。下面我們演示下Gateway結合註冊中心如何使用默認的動態路由和過濾器。
使用動態路由
在pom.xml中添加相關依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐netflix‐eureka‐client</artifactId>
</dependency>
添加application-eureka.yml配置文件:
server:
port: 9201
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啓從註冊中心動態創建路由的功能
lower-case‐service‐id: true #使用小寫服務名,默認是大寫
eureka:
client:
service‐url:
defaultZone: http://localhost:8001/eureka/
logging:
level:
org.springframework.cloud.gateway: debug
使用application-eureka.yml配置文件啓動api-gateway服務,訪問 http://localhost:9201/user-service/user/1
,可以路由到user-service的 http://localhost:8201/user/1
處。
使用過濾器
在結合註冊中心使用過濾器的時候,我們需要注意的是uri的協議爲 IBhttps://blog.csdn.net/swingwang/article/details/72887367,這樣才能啓用 Gateway的負載均衡功能。
修改application-eureka.yml文件,使用了PrefixPath過濾器,會爲所有GET請求路徑添加/user路徑並路由;
server:
port: 9201
spring:
application:
name: api‐gateway
cloud:
gateway:
routes:
‐ id: prefixpath_route
uri: ib://user‐service #此處需要使用ib協議
predicates:
‐ Method=GET
filters:
‐ PrefixPath=/user
discovery:
locator:
enabled: true
eureka:
client:
service‐url:
defaultZone: http://localhost:8001/eureka/
logging:
level:
org.springframework.cloud.gateway: debug
使用application-eureka.yml配置文件啓動api-gateway服務,訪問 http://localhost:9201/1
,可以路由到user-service的http://localhost:8201/user/1
處。
6. Spring-Cloud-Gateway 源碼解析
6.1 Spring GateWay架構
Spring Cloud提供了兩套方便我們編寫網關的中間件,分別是zuul和Spring GateWay,在zuul1的IO模型是使用BIO(圖1-1)。而zuul2對IO模型使用NIO進行了重構(圖1-2)。而 Spring GateWay的IO模型是使用NIO。而在Netflix發佈zuul2的時候Spring Cloud已經開始不集成到Spring Cloud中,因爲Spring Cloud 等着zuul2集成太久,纔有了Spring Gateway。Spring GateWay的架構是基於Spring webflux的基礎上開發的。而對webflux 的RP中涉及的Back Pressure、Stream、asynchronous好處就不多了。
圖1-1
圖1-2
6.2 Spring GateWay 轉發請求過程
轉發請求過程
在Spring mvc是通過HandlerMapping解析請求鏈接,然後根據請求鏈接找到執行這個請求Controller類 。而在Spring GateWay中也是使用HandlerMapping對請求的鏈接進行解析匹配對應的Route進行代理轉發到對應的服務。下圖爲整個請求的流程,用戶請求先通 過DispatcherHandler找到對應GateWwayHandlerMapping,再通過 GateWwayHandlerMapping解析匹配到對應的Handler。Handler處理完後,再經過 Filter,最終到Proxied Service.
轉發請求過程代碼分析
- 請求先由DispatcherHanlder進行處理,DispatcherHanlder初始化的時候會 從IOC中查找實現HandlerMapping接口的實現類。然後保存到內部變量 handlerMappings中,DispatcerHandler調用Handler方法迭代handlerMappings 中的HandlerMapping,
- 這裏只講解RoutePredicateHandlerMapping,因此然後調用 RoutePredicateHandlerMapping中的獲取路由的方法,當 RoutePredicateHandlerMapping獲取到對應的路由的時候會將Route存儲到 ServerWebExchanges的屬性中,然後返回實現了WebHandler接口的 FilteringWebHandler。FilteringWebHandler是一個存放過濾器的Handler。
- 最後DispatcherHanlder通過SimpleHandlerAdapter適配器的方式調用 FilteringWebHandler的handler方法,FilteringWebHandler調用所有的過濾器, 包括proxy filter。通過proxyFilter請求被代理的服務。處理完畢後,並將Response 響應回去。
通過流程的分析,這裏我們可以學習到適配的設計模式的使用。GateWay中的 HandlerMapping有RouterFunctionMapping、RoutePredicateHandlerMapping等。 這些HandlerMapping返回的結果都是不一樣的。那麼DispatcherHanlder的後續處理也不 會一樣。那麼可以通過適配器的方式,根據HandlerMapping返回的結果進行適配調用。
下面爲handler類關係圖。這裏主要涉及到Spring GateWay相關類的探討。如:Spring Webflux使用到的RouteFuntionMapping和SimpleUrlHandlerMapping等不做探討。
Spring GateWay 主要相關類思維導圖