文章目錄
特別聲明:整理自慕課網大目師兄的微服務視頻,鏈接:https://coding.imooc.com/learn/list/358.html
前情提要:在nacos上註冊了content-center和user-center兩個服務,content-center使用Feign調用user-center服務,使用Ribbon做負載均衡,sentinel實現服務容錯來保護服務自己,微服務架構的像模像樣了,但是我們還需要網關Gateway
1.什麼是網關?
一句話,客戶端訪問後臺的統一入口
2.爲什麼我們需要網關?
如果沒有網關,客戶端與每個微服務直接通訊,會造成以下的不便
- 登錄認證不便
如果微服務很多,每個微服務裏都要有登錄認證這一套邏輯,這就很煩了;如果有網關,把登錄認證放到網關裏實現,只需要做一次,很方便 - 客戶端迭代不便
如果微服務的地址,域名改變了,客戶端要做大量的改動;
用網關就不一樣了,客戶端只需要知道網關的地址就行了,請求都是經過網關轉發,客戶端不用關心微服務地址的改變 - 無法訪問特殊協議的微服務
如果有些微服務使用了客戶端不友好的協議,沒有網關去轉換協議,就無法訪問這些微服務了
3.Spring Cloud Gateway
- 是spring cloud的第二代網關,未來會取代第一代網關zuul
- 基於Netty、Reactor以及WebFlux構建
- 性能強勁,是第一代網關zuul的1.6倍
- 功能強大,內置很多實用功能,例如轉發,監控,限流
- 不支持springboot 1.x
- 還在改進當中,下面寫的這版本沒錯,下版本就不一定對了
4.創建Spring Cloud Gateway項目
4.1 IDEAJ 創建gateway項目
找不到的可以直接搜索一下
4.2 添加依賴
- 修改springboot版本爲2.1.6.RELEASE,和其它微服務保持一直
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
- 添加spring-cloud-alibaba
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com.alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<com.alibaba.cloud.version>0.9.0.RELEASE</com.alibaba.cloud.version>
- 添加springboot 監控 actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 添加nacos客戶端
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4.3 寫配置
- 端口號
server:
port: 8040
- 應用名稱
spring:
application:
name: gateway
- nacos 註冊地址
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 118.31.11.178:8848
- 開啓gateway通過服務發現組件找到其它服務
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 118.31.11.178:8848
gateway:
discovery:
locator:
# 讓gateway通過服務發現組件找到其它的微服務
enabled: true
- 開啓actuator的配置
management:
endpoints:
web:
exposure:
# 暴露出所有actuator監控的端點
include: '*'
endpoint:
health:
show-details: always
4.4 啓動gateway項目,測試通過網關訪問content和user
- 訪問user-center的接口:http://localhost:8040/user-center//reciteHis/testAno
- 訪問content-center接口:http://localhost:8040/content-center/poem/list?ids=1,2
轉發規律:訪問 ${gateway_url}/{微服務X}/** 實際轉發到 微服務X的/** 路徑
5.spring cloud gateway的兩大核心
5.1 gateway的架構圖
由圖可見,網關的兩大核心,1.轉發請求(路由,routes),2.過濾器
5.2 核心之一 routes 路由
5.2.1 路由nacos其它微服務
- 開啓 gateway 發現服務註冊組件上其它微服務的自動路由
效果:訪問 ${gateway_url}/{微服務X}/** -------> 微服務X的/** 路徑
spring:
cloud:
gateway:
discovery:
locator:
# 開啓 gateway 發現服務註冊組件上其它微服務的自動路由
enabled: true
5.2.2 自定義路由
5.2.2.1 示例 (可以配置多個):
如果有多個路由,請求會被第一個符合條件的路由轉發走
將 http://localhost:8040/baidu/search/error.html?chid=wx 路由轉發到 https://www.baidu.com/search/error.html,不帶chid=wx參數的不轉發
spring:
cloud:
gateway:
routes:
- id: test_baidu_route
uri: https://www.baidu.com
predicates:
- Query=chid,wx
- Path=/baidu/**
filters:
- name: RewritePath
args:
regexp: /baidu/(?<remaining>.*)
replacement: /${remaining}
這個配置表示的含義:
-
id
這個路由的唯一標識,隨便定義 -
uri
轉發的去處,就是要轉發到哪裏去 -
predicates,
這是斷言,也叫謂詞工廠,這裏使用了兩個謂詞,被轉發的url必須滿足這兩個條件
第一個是Query,Query=chid,wx表示,請求gateway的鏈接必須要有參數 chid=wx纔會被轉發
第一個是Path,Path=/baidu/**表示,請求gateway的鏈接必須是 http://localhost:8040/baidu/****************,gateway_url後面必須是/baidu/的纔會被轉發
內置的謂詞工廠有11個,起到不同的作用,如果這11個內置的謂詞不能滿足你的需求,你也可以寫一個謂詞工廠。這裏是官方文檔:https://cloud.spring.io/spring-cloud-gateway/reference/html/#gateway-request-predicates-factories -
filters
過濾器,這裏只配置了一個過濾器RewritePath,如果不配置這個路徑替換過濾器
http://localhost:8040/baidu/search/error.html?chid=wx被轉發到 https://www.baidu.com/baidu/search/error.html,你會發現中間多了一個 /baidu/,這就不對了,所以這個過濾器就是把 /baidu/替換掉的邏輯,使最終的url是 https://www.baidu.com/search/error.html,一個可以正常訪問的url
內置的過濾器工廠有二三十個,各種功能都有,同樣你也可以自定義符合自己需求的過濾器。這裏是官方文檔:https://cloud.spring.io/spring-cloud-gateway/reference/html/#_rewritepath_gatewayfilter_factory -
測試正常的轉發
http://localhost:8040/baidu/search/error.html?chid=wx,沒錯,是baidu的返回
-
測試參數 chid=wx 不正確
http://localhost:8040/baidu/search/error.html?chid=wxx,說明predicate Query起作用了
-
測試不以 /baidu/ 開頭
http://localhost:8040/search/error.html?chid=wx,說明 predicate Path 起作用了
5.2.2.2 內置的Predicate謂詞工廠
官方文檔鏈接:https://cloud.spring.io/spring-cloud-gateway/reference/html/#gateway-request-predicates-factories
- After
技巧:時間可使用 System.out.println(ZonedDateTime.now()); 打印
spring:
cloud:
gateway:
routes:
- id: after_route
uri: lb://user-center
predicates:
# 當且僅當請求時的時間After配置的時間時,纔會轉發到用戶微服務
# 目前配置不會進該路由配置,所以返回404
# 將時間改成 < now的時間,則訪問localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- After=2030-01-20T17:42:47.789-07:00[America/Denver]
- Before
spring:
cloud:
gateway:
routes:
- id: before_route
uri: lb://user-center
predicates:
# 當且僅當請求時的時間Before配置的時間時,纔會轉發到用戶微服務
# 目前配置不會進該路由配置,所以返回404
# 將時間改成 > now的時間,則訪問localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Before=2018-01-20T17:42:47.789-07:00[America/Denver]
- Between
spring:
cloud:
gateway:
routes:
- id: between_route
uri: lb://user-center
predicates:
# 當且僅當請求時的時間Between配置的時間時,纔會轉發到用戶微服務
# 因此,訪問localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2027-01-21T17:42:47.789-07:00[America/Denver]
- Cookie
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: lb://user-center
predicates:
# 當且僅當帶有名爲somecookie,並且值符合正則ch.p的Cookie時,纔會轉發到用戶微服務
# 如Cookie滿足條件,則訪問http://localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Cookie=somecookie, ch.p
- Header
spring:
cloud:
gateway:
routes:
- id: header_route
uri: lb://user-center
predicates:
# 當且僅當帶有名爲X-Request-Id,並且值符合正則\d+的Header時,纔會轉發到用戶微服務
# 如Header滿足條件,則訪問http://localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Header=X-Request-Id, \d+
- Host
spring:
cloud:
gateway:
routes:
- id: host_route
uri: lb://user-center
predicates:
# 當且僅當名爲Host的Header符合**.somehost.org或**.anotherhost.org時,纔會轉發用戶微服務
# 如Host滿足條件,則訪問http://localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Host=**.somehost.org,**.anotherhost.org
- Method
spring:
cloud:
gateway:
routes:
- id: method_route
uri: lb://user-center
predicates:
# 當且僅當HTTP請求方法是GET時,纔會轉發用戶微服務
# 如請求方法滿足條件,訪問http://localhost:8040/** -> user-center/**
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Method=GET
- Path
spring:
cloud:
gateway:
routes:
- id: path_route
uri: lb://user-center
predicates:
# 當且僅當訪問路徑是/users/*或者/some-path/**,纔會轉發用戶微服務
# segment是一個特殊的佔位符,單層路徑匹配
# eg. 訪問http://localhost:8040/users/1 -> user-center/users/1
- Path=/users/{segment},/some-path/**
- Query
示例1
spring:
cloud:
gateway:
routes:
- id: query_route
uri: lb://user-center
predicates:
# 當且僅當請求帶有baz的參數,纔會轉發到用戶微服務
# eg. 訪問http://localhost:8040/users/1?baz=xx -> user-center的/users/1
- Query=baz
示例2
spring:
cloud:
gateway:
routes:
- id: query_route
uri: lb://user-center
predicates:
# 當且僅當請求帶有名爲foo的參數,且參數值符合正則ba.,纔會轉發到用戶微服務
# eg. 訪問http://localhost:8040/users/1?baz=baz -> user-center的/users/1?baz=baz
- Query=foo, ba.
- RemoteAddr
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: lb://user-center
predicates:
# 當且僅當請求IP是192.168.1.1/24網段,例如192.168.1.10,纔會轉發到用戶微服務
# eg. 訪問http://localhost:8040/users/1 -> user-center的/users/1
- RemoteAddr=192.168.1.1/24
組合使用
spring:
cloud:
gateway:
routes:
- id: host_foo_path_headers_to_httpbin
uri: http://ityouknow.com
predicates:
- Host=**.foo.org
- Path=/headers
- Method=GET
- Header=X-Request-Id, \d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
相關代碼
如果想知道具體配置的細則,看源碼是最直接有效的方法
5.2.2.3 自定義Predicate謂詞工廠
參照這些內置predicate的源碼實現,照葫蘆畫瓢,代碼並不多,寫一個類就行
5.2.2.4 內置的filters過濾器
官方文檔:https://cloud.spring.io/spring-cloud-gateway/reference/html/#_gatewayfilter_factories,參照文檔配置使用就行
也可以看大目師兄的手記:https://www.imooc.com/article/290816
5.2.2.5 自定義filters過濾器
也是參照內置的filters去實現
5.3 核心之二 filters 全局過濾器
上面提到的過濾器都是對單個路由起作用的gatewayFilter,全局過濾器是對所有路由都有用的過濾器globalFilter
經過我的測試,全局過濾器默認都是生效的,包括內置的全局過濾器和自定義的全局過濾器
5.3.1 內置全局過濾器
官方文檔:https://cloud.spring.io/spring-cloud-gateway/reference/html/#_global_filters
大目師兄手記:https://www.imooc.com/article/290821
5.3.2 自定義全局過濾器並使用
我自定一個類 GlobalFilterConfiguration
package com.zengchen.gateway.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Configuration
@Slf4j
public class GlobalFilterConfiguration {
/**
* 給每個請求增加個header requestHeader=111111
* @return
*/
@Bean
@Order(1)
public GlobalFilter addHeaderArPre() {
return (exchange, chain) -> {
log.info("do **pre** GlobalFilter addHeaderArPre");
ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
requestBuilder.header("requestHeader","111111");
ServerWebExchange newExchange = exchange.mutate().request(requestBuilder.build()).build();
return chain.filter(newExchange).then(Mono.fromRunnable(() -> {
log.info("do **post** GlobalFilter addHeaderArPre");
}));
};
}
/**
* 給每個請求返回的時候,增加header
* @return
*/
@Bean
@Order(2)
public GlobalFilter addHeaderAtPost() {
return (exchange, chain) -> {
log.info("do **pre** GlobalFilter addHeaderAtPost");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("do **post** GlobalFilter addHeaderAtPost");
exchange.getResponse().getHeaders().add("responseHeader","666666");
}));
};
}
}
這樣就ok了,不用寫任何配置,重啓項目,直接就對所有的請求有效了
返回的responseHeader已經有了666666,至於請求的header有沒有添加進去,可以在其它執行順序排在它後面的過濾器裏面打斷點查看,我偷偷看過,有的
5.3.2 過濾器的執行順序
上面請求打的日誌,addHeaderArPre排在addHeaderAtPost之前執行,因爲addHeaderArPre的order是1,addHeaderAtPost的order是2,數值小的先執行。對於post,方法返回時候執行順序又倒過來了,有點繞,執行順序得看清楚了
對於gatewayFilter,寫配置的時候,誰寫在第一個,它的order就是1,後面的順着排,2,3,4、、、、
6.springboot actuator對gateway的監控
創建項目的時候,我們已經添加了actuator監控組件
訪問:http://localhost:8040/actuator/,最後一個就是gateway部分的入口,但是不能直接訪問,我試一下404,下面介紹可以訪問的端點
- 可以訪問的端點
路徑 | 描述 |
---|---|
globalfilters | GET 展示所有的全局過濾器 |
routefilters | GET 展示所有的 gatewayFilter |
routes | GET 展示路由列表 |
routes/{id} | GET 展示指定 id 的路由的信息 |
refresh | POST 清空路由緩存 |
routes/{id} | POST 新增一個路由,消息體如下 |
routes/{id} | DELETE 刪除一個路由 |
新增路由消息體:
{
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/test"
}
}
],
"filters": [
{
"name": "AddRequestHeader",
"args": {
"_genkey_0": "X-Request-Foo",
"_genkey_1": "Bar"
}
},
{
"name": "PreLog",
"args": {
"_genkey_0": "a",
"_genkey_1": "b"
}
}
],
"uri": "https://www.itmuch.com",
"order": 0
}
- 查看全局過濾器
http://localhost:8040/actuator/gateway/globalfilters,還可以看到內置globalFilters的order,畫紅框的就是我自定義的兩個全局過濾器,沒有顯示order,我也不知道爲什麼,看一下內置全局過濾器源碼,可能會有答案
- 查看所有的路由
http://localhost:8040/actuator/gateway/routes,有四個,正好,兩個微服務+gateway自己+自定義的一個路由