概述
微服務可能分佈在不同的主機上,這樣有許多缺點:前端需要硬編碼呼叫不同地址的微服務很麻煩;存在跨域訪問的問題;微服務地址直接暴露是不安全的。還有所以需要為前端提供一個統一的訪問入口。Gateway 就是用於解決以上問題的框架。Gateway 本身是一個微服務,要註冊到 Eureka 中。
入門案例
- maven 依賴:
<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>
</dependencies>
- 啟動類新增 @EnableDiscoveryClient 註解
- 配置檔案:基本的 Eureka 配置 + 路由配置
server:
port: 10010
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route # 路由的標識 ID,任意名稱都可以
uri: http://127.0.0.1:9092 # 路由的轉發地址
predicates:
- Path=/user/** # 路由的對映範圍
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
- 是否配置成功?
啟動例項後能在 Eureka 註冊中心看到註冊,http://localhost:10010/user/**
的請求會被轉發到 http://localhost:9092/user/**
。
動態路由
入門案例中使用的是靜態IP 地址,下面是用服務名稱代替具體 IP,這樣避免直接使用 IP,並可以新增負載均衡。
只需要將配置中的 uri 更換為「loadbalance 協議」
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route # 路由的標識 ID,任意名稱都可以
uri: lb://USER-SERVICE # 路由的轉發地址
predicates:
- Path=/user/** # 路由的對映範圍
路由規則
- 新增字首:
http://localhost:10010/**
-->lb://USER-SERVICE/user/**
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route # 路由的標識 ID,任意名稱都可以
uri: lb://USER-SERVICE # 路由的轉發地址
predicates:
- Path=/** # 路由的對映範圍
filters:
- PrefixPath=/user
- 去除字首:
http://localhost:10010/api/api/user/**
-->lb://USER-SERVICE/user/**
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route # 路由的標識 ID,任意名稱都可以
uri: lb://USER-SERVICE # 路由的轉發地址
predicates:
- Path=/api/api/user/** # 路由的對映範圍
filters:
- StripPrefix=2 # 去除兩層字首
過濾器
前面修改請求其實都是通過 Gateway 的過濾器實現的,上面用到的過濾器為 StripPrefixGatewayFilterFactory 產生的過濾器。Gateway 提供了幾十種過濾器,過濾器可以過濾指定規則的請求,也可以過濾所有請求。
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=this-is-a-header, Have-Fun
這個過濾器可以為所有請求新增一個響應頭,this-is-a-header: Have-Fun
。
路由器分為區域性路由和全域性路由,區域性路由分為兩種(上面展示的兩種):
- spring.cloud.gateway.routes.- id.filters
- spring.cloud.gateway.default-filters (功能等價於全域性過濾器)
全域性過濾器:不在配置檔案中配置,需要實現 GlobalFilter 介面。
生命週期
過濾器類似於攔截器,但是隻在微服務處理前後運作,請求轉發不會觸發過濾器。
自定義區域性過濾器
自定義過濾器需要實現 AbstractGatewayFilterFactory 抽象類。該類使用內部類接受配置檔案中的引數,需要給這個內部類的提供 Getter/Setter 方法。
package com.example.gateway.filter;
import java.util.Arrays;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
@Component
public class MyMyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyMyGatewayFilterFactory.Config> {
public MyMyGatewayFilterFactory() {
super(Config.class);
}
// 這個方法指定配置檔案注入 Config 的哪一個欄位
public List<String> shortcutFieldOrder() {
return Arrays.asList("param");
}
// 由於接受配置檔案引數
public static class Config {
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
// 此為過濾器邏輯,返回值是一個過濾方法,可以使用 Lambda 表示式。
// exchange 引數可以獲取請求內容。chain 用於執行請求。
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 前置處理
ServerHttpRequest request = exchange.getRequest();
if (request.getQueryParams().containsKey(config.param)) {
request.getQueryParams().get(config.param)
.forEach(o -> System.out.printf("區域性過濾器:%s = %s \n", config.param, o));
}
return chain.filter(exchange); // 執行請求
};
}
}
這裡我們使用配置檔案將 "name" 注入到 Config.param 欄位上。注意配置中使用該過濾器類的字首來注入。比如 MyMyGatewayFilterFactory 的字首為 MyMy。過濾器類的命名格式是固定的,為 XxxxGatewayFilterFactory,需要使用 @Component 注入到 Spring 容器中,泛型使用 Config 內部類。
spring:
cloud:
gateway:
routes:
- id: user-service-route # 路由的標識 ID,任意名稱都可以
uri: lb://USER-SERVICE # 路由的轉發地址
predicates:
- Path=/api/api/user/** # 路由的對映範圍
filters:
- StripPrefix=2 # 去除兩層字首
- MyMy=name
default-filters:
- AddResponseHeader=this-is-a-header, Have-Fun
自定義全域性過濾器
實現 GlobalFilter, Ordered 介面並注入到 Spring 容器中。
package com.example.gateway.filter;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
// 設定執行的優先順序,數字越小優先順序越高
@Override
public int getOrder() {
return 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義全域性過濾器 MyGlobalFilter 執行");
String token = exchange.getRequest().getHeaders().getFirst("token");
// 如果沒有 token 將響應狀態設定為「未驗證 401」,並直接返回,不執行後面的操作。
if (StringUtils.isBlank(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}
負載均衡和熔斷配置
Gateway 預設整合了 Ribbon 和 Hystrix。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 #服務降級超時時間
circuitBreaker:
errorThresholdPercentage: 50 # 觸發熔斷錯誤比例閾值,預設值50%
sleepWindowInMilliseconds: 10000 # 熔斷後休眠時長,預設值5秒
requestVolumeThreshold: 10 # 熔斷觸發最小請求次數,預設值是20
ribbon:
ConnectTimeout: 1000 # 連線超時時長
ReadTimeout: 2000 # 資料通訊超時時長
MaxAutoRetries: 0 # 當前伺服器的重試次數
MaxAutoRetriesNextServer: 0 # 重試多少次服務
OkToRetryOnAllOperations: false # 是否對所有的請求方式都重試
跨域訪問配置
微服務會面臨跨域訪問(比如埠不同)的問題,預設情況下服務不允許別的域訪問,我們需要在配置中設定「白名單」告知從哪些域來的請求是被允許的。
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]': # 哪些請求可以跨域
allowedOrigins: # 跨域白名單
- "http://docs.spring.io"
allowedMethods: # 允許的請求方式
- GET
一個常見的情況是前後端分離,前端後端部署在不同埠。比如上面配置中 http://docs.spring.io
為前端的域,完成這個配置之後,前端就可以正常使用 Ajax 訪問其他域的所有微服務。
Gateway 高可用
啟動多個 Gateway 例項,實現微服務內部訪問的高可用。但是 Gateway 主要適用於直接暴露給客戶端,僅內部高可用並不是想要的,這時候需要使用 Nginx 代理 Gateway。
Gateway 和 Feign 的區別
Gateway 是作為整體微服務暴露給客戶端的訪問入口,通常用作許可權鑑定和流量控制。Feign 是微服務內部之間互相呼叫的入口。一句話說就是 Gateway、Feign 一外一內。
尊重原創,轉載請標明出處。