微服務系列導航
- 第一篇:初學者如何快速入門微服務,面試前如何快速熟悉微服務
- 第二篇:微服務Spring Cloud Alibaba之Nacos篇, Nacos 就是注冊中心 + 配置中心的組合
- 第三篇:微服務Spring Cloud Alibaba之Sentinel篇,使用熔斷器防止雪崩
- 第四篇:Spring Cloud Gateway面試攻略,微服務網關的作用以及案例
此文目錄
-
- 微服務系列導航
- 一、什麼是微服務網關
- 二、支援特征
- 三、工作原理
- 四、面試題
-
- 4.1 過濾器和網關的對比
- 4.2 zuul和spring cloud gateway的對比
- 4.3 網關與nginx差別
- 4.4 gateway的組成
- 五、項目實戰
-
- 5.1 MateCloud項目源碼
- 5.2核心依賴
- 5.3 網關統一認證
- 5.4 網關動态路由
- 六、寫在最後
一、什麼是微服務網關
Spring Cloud Gateway 是 Spring 官方基于 Spring 5.x,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,Spring Cloud Gateway 旨在為微服務架構提供一種簡單而有效的統一的 API 路由管理方式。Spring Cloud Gateway 作為 Spring Cloud 生态系統中的網關,目标是替代 Netflix Zuul,其不僅提供統一的路由方式,并且基于 Filter 鍊的方式提供了網關基本的功能,例如:安全性、監視/名額和彈性。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cscXSYllbW1mWxokMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4UzMyEzN0ATM4ITNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
二、支援特征
- 基于 Spring Framework 5,Project Reactor和Spring Boot 2.0之上
- 動态路由
- Predicates 和 Filters 作用于特定路由
- 斷路器內建
- 內建 Spring Cloud DiscoveryClient
- 易于編寫的 Predicates 和 Filters
- 限流
- 路徑重寫
三、工作原理
下圖從總體上概述了Spring Cloud Gateway的工作方式:
用戶端向 Spring Cloud Gateway 送出請求。然後在 Gateway Handler Mapping 中找到與請求相比對的路由,将其發送到 Gateway Web Handler。Handler 再通過指定的過濾器鍊來将請求發送到我們實際的服務執行業務邏輯,然後傳回。
過濾器之間用虛線分開是因為過濾器可能會在發送代理請求之前(pre)或之後(post)執行業務邏輯。
四、面試題
4.1 過濾器和網關的對比
- 過濾器:對單個伺服器的請求進行攔截控制
- 網關:對所有的伺服器的請求進行攔截控制
4.2 zuul和spring cloud gateway的對比
- zuul:是Netflix的,是基于servlet實作的,阻塞式的api,不支援長連接配接。
- gateway:是springcloud自己研制的微服務網關,是基于Spring5建構,能夠實作響應式非阻塞式的Api,支援長連接配接
4.3 網關與nginx差別
- 相同點:都是可以實作對api接口的攔截,負載均衡、反向代理、請求過濾等,可以實作和網關一樣的效果。
- 不同點:
- Nginx采用C語言編寫,Gateway屬于Java語言編寫的, 能夠更好讓我們使用java語言來實作對請求的處理。
- Nginx 屬于伺服器端負載均衡器。
- Gateway 屬于本地負載均衡器。
4.4 gateway的組成
- 路由 : 網關的基本子產品,有ID,目标URI,一組斷言和一組過濾器組成
- 斷言:就是通路該旅遊的通路規則,可以用來比對來自http請求的任何内容,例如headers或者參數
- 過濾器:這個就是我們平時說的過濾器,用來過濾一些請求的,gateway有自己預設的過濾器,具體請參考官網,我們也可以自定義過濾器,但是要實作兩個接口,ordered和globalfilter
五、項目實戰
5.1 MateCloud項目源碼
項目 | GITHUB | 碼雲 |
---|---|---|
MateCloud後端源碼 | https://github.com/matevip/matecloud | https://gitee.com/matevip/matecloud |
Artemis前端源碼 | https://github.com/matevip/artemis | https://gitee.com/matevip/artemis |
5.2核心依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
注意:
- Spring Cloud Gateway 不使用 Web 作為伺服器,而是 使用 WebFlux 作為伺服器 ,Gateway 項目已經依賴了 starter-webflux,是以這裡千萬不要依賴 starter-web
- 由于過濾器等功能依然需要 Servlet 支援,故這裡還需要依賴javax.servlet:javax.servlet-api
位址:https://gitee.com/matevip/matecloud/blob/dev/mate-gateway/pom.xml
5.3 網關統一認證
package vip.mate.gateway.filter;
import io.jsonwebtoken.Claims;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import vip.mate.core.cloud.props.MateApiProperties;
import vip.mate.core.common.constant.MateConstant;
import vip.mate.core.common.constant.Oauth2Constant;
import vip.mate.core.common.util.ResponseUtil;
import vip.mate.core.common.util.SecurityUtil;
import vip.mate.core.common.util.StringPool;
import vip.mate.core.common.util.TokenUtil;
import vip.mate.core.redis.core.RedisService;
/**
* 網關統一的token驗證
*
* @author pangu
* @since 1.5.8
*/
@Slf4j
@Component
@AllArgsConstructor
public class PreUaaFilter implements GlobalFilter, Ordered {
private final MateApiProperties mateApiProperties;
private final RedisService redisService;
/**
* 路徑字首以/mate開頭,如mate-system
*/
public static final String PATH_PREFIX = "/mate";
/**
* 索引自1開頭檢索,跳過第一個字元就是檢索的字元的問題
*/
public static final int FROM_INDEX = 1;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 如果未啟用網關驗證,則跳過
if (!mateApiProperties.getEnable()) {
return chain.filter(exchange);
}
// 如果在忽略的url裡,則跳過
String path = replacePrefix(exchange.getRequest().getURI().getPath());
String requestUrl = exchange.getRequest().getURI().getRawPath();
if (ignore(path) || ignore(requestUrl)) {
return chain.filter(exchange);
}
// 驗證token是否有效
ServerHttpResponse resp = exchange.getResponse();
String headerToken = exchange.getRequest().getHeaders().getFirst(Oauth2Constant.HEADER_TOKEN);
if (headerToken == null) {
return unauthorized(resp, "沒有攜帶Token資訊!");
}
String token = TokenUtil.getToken(headerToken);
Claims claims = SecurityUtil.getClaims(token);
if (claims == null) {
return unauthorized(resp, "token已過期或驗證不正确!");
}
// 判斷token是否存在于redis,對于隻允許一台裝置場景适用。
// 如隻允許一台裝置登入,需要在登入成功後,查詢key是否存在,如存在,則删除此key,提供思路。
boolean hasKey = redisService.hasKey("auth:" + token);
log.debug("查詢token是否存在: " + hasKey);
if (!hasKey) {
return unauthorized(resp, "登入逾時,請重新登入");
}
return chain.filter(exchange);
}
/**
* 檢查是否忽略url
* @param path 路徑
* @return boolean
*/
private boolean ignore(String path) {
return mateApiProperties.getIgnoreUrl().stream()
.map(url -> url.replace("/**", ""))
.anyMatch(path::startsWith);
}
/**
* 移除子產品字首
* @param path 路徑
* @return String
*/
private String replacePrefix(String path) {
if (path.startsWith(PATH_PREFIX)) {
return path.substring(path.indexOf(StringPool.SLASH, FROM_INDEX));
}
return path;
}
private Mono<Void> unauthorized(ServerHttpResponse resp, String msg) {
return ResponseUtil.webFluxResponseWriter(resp, MateConstant.JSON_UTF8, HttpStatus.UNAUTHORIZED, msg); }
@Override
public int getOrder() {
return MateConstant.MATE_UAA_FILTER_ORDER;
}
}
5.4 網關動态路由
package vip.mate.gateway.service.impl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* 動态更新路由網關service
* 1)實作一個Spring提供的事件推送接口ApplicationEventPublisherAware
* 2)提供動态路由的基礎方法,可通過擷取bean操作該類的方法。該類提供新增路由、更新路由、删除路由,然後實作釋出的功能。
* @author pangu
*/
@Slf4j
@Service
@AllArgsConstructor
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
private final RouteDefinitionWriter routeDefinitionWriter;
/**
* 釋出事件
*/
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 删除路由
* @param id
* @return
*/
public String delete(String id) {
try {
log.info("gateway delete route id {}",id);
this.routeDefinitionWriter.delete(Mono.just(id));
return "delete success";
} catch (Exception e) {
return "delete fail";
}
}
/**
* 更新路由
* @param definition
* @return
*/
public String update(RouteDefinition definition) {
try {
log.info("gateway update route {}",definition);
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
/**
* 增加路由
* @param definition
* @return
*/
public String add(RouteDefinition definition) {
log.info("gateway add route {}",definition);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
}
六、寫在最後
網關更多功能可以檢視官方文檔:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
和MateCloud的網關實戰源碼,去探索一下。
送您一句話:安全生産、人人有責