Spring Cloud Gateway(服务网关)
一、灵魂拷问:为什么要请这位"霸道总裁"?(Gateway的介绍)
1. 为什么要用Gateway?(重点中的重点)
想象一下,你是一个前端Vue页面的负责人。在没有网关的"史前时代",你的日子是怎么过的?
- 硬编码的痛 :你的代码里写满了
http://192.168.1.100:8080这种硬邦邦的地址。万一后端微服务搬家了(IP变了)或者扩容了(端口变了),你得重新打包发布,简直是噩梦。 - 负载均衡的梦魇:如果后端有三个微服务实例,你是要自己写轮询算法吗?还是在Nginx里配到头晕?
没有网关的架构图(惨状):
text
Vue前端 (满头大汗)
|
(硬编码IP:Port,写死在代码里)
|
-----------------------
| | |
微服务1 微服务2 微服务3
(挂了) (忙死) (闲死)
有了Gateway之后:
Gateway就是微服务架构里的"前台接待"+"安保队长"。所有请求先找它,它来决定把你带到哪个房间。
2. 什么是Gateway?
- 官方定义 :Spring Cloud官方基于 WebFlux (底层是Netty,也就是传说中的Reactor NIO) 开发的亲儿子,专门用来替换掉上一代老古董Zuul的。
- 核心技能:路由(指路)、过滤(安检)、限流(防暴乱)。
- 为什么比Zuul强? Zuul 1.x是同步阻塞的(一个人占一个线程,累死),Gateway是异步非阻塞的(Netty加持,少线程干大事),性能起飞。
3. 启动器(依赖)
记住这个咒语,千万别念错:
- 要加 :
spring-cloud-starter-gateway - 千万别加 :
spring-boot-starter-web - 原因:Gateway是Netty驱动的,Web是Tomcat驱动的。这俩是死对头,加在一起会打架,启动直接报错教你做人。
二、搭建工程:给总裁安个家
1. pom.xml(招兵买马)
xml
<dependencies>
<!-- 网关核心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 注册中心(让网关知道微服务在哪) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
2. application.yml(立规矩)
yaml
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.61.132:8848 # 告诉Nacos在哪,我要去登记
application:
name: api-gateway # 网关的大名
3. 启动类(剪彩仪式)
java
@SpringBootApplication
@EnableDiscoveryClient // 别忘了这个,不然就是个瞎子,找不到微服务
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class);
}
}
三、路由(Routing):指路艺术
路由就是告诉网关:"来了这种请求,往哪里送"。
1. 内置断言工厂(Predicates)
断言就是判断条件。只有条件满足了(True),才进行路由转发。
- lb://:这是神技!意思是"负载均衡",后面跟服务名,网关会自动去Nacos拉取服务列表,然后挑一个转发。再也不用写死IP了!
yaml
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer # ID必须唯一,随便起,别重复
# uri: http://127.0.0.1:80 # 这种硬编码方式已经被淘汰了
uri: lb://sentinel-consumer # 负载均衡,去Nacos找 sentinel-consumer 服务
predicates:
- Path=/consumer/** # 只要路径是 /consumer/ 开头的,就归我管
- After=2022-04-09T13:20:54.957+08:00[Asia/Shanghai] # 只有在这个时间之后的请求才放行(防穿越?)
2. 自定义断言工厂(DIY判断逻辑)
如果内置的不够用,比如你想搞个"年龄断言"(只有18-60岁才能访问),那就自己写。
- 核心思路 :继承
AbstractRoutePredicateFactory。
java
// 伪代码示意,具体抄作业
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
// 构造方法...
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 获取参数里的年龄
String ageStr = exchange.getRequest().getQueryParams().getFirst("age");
int age = Integer.parseInt(ageStr);
// 判断是否在范围内
return age >= config.getMinAge() && age <= config.getMaxAge();
};
}
// 配置类...
}
配置使用:
yaml
predicates:
- Path=/consumer/**
- Age=18,60 # 调用你的自定义断言
四、过滤(Filtering):安检与整容
路由是"去哪",过滤是"路上干什么"。比如:加个头、删个脚、查个证。
1. 内置过滤器工厂
最常用的是 StripPrefix,就是"脱衣舞"------把URL前面那一截去掉再转发。
- 场景 :网关收到
/consumer/api/doSomething,但微服务只认识/api/doSomething。 - 配置 :
StripPrefix=1(去掉第一层/consumer)。
yaml
filters:
- StripPrefix=1
2. 自定义全局过滤器(GlobalFilter)------ 大内总管
这是"大内总管",对所有请求生效。通常用来做统一的鉴权、日志记录。
- 核心接口 :
GlobalFilter+Ordered
java
package com.haogu.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author ShanYu
* @version 1.0
* @description: 自定义全局过滤器
*
*/
//@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(token == null){
Map<String, Object> result = new HashMap<>();
result.put("status", 401);
result.put("msg", "未登录");
return response(exchange.getResponse(), result);
}
return chain.filter(exchange);//放行
}
/**
* 往前端返回json串
* @param response
* @param data
* @return
*/
private Mono<Void> response(ServerHttpResponse response, Object data) {
String jsonData = null;
try {
jsonData = new ObjectMapper().writeValueAsString(data);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer buffer =
response.bufferFactory().wrap(jsonData.getBytes(StandardCharsets.UTF_8));
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
//返回值越小该过滤器的执行优先级越高
@Override
public int getOrder() {
return 0;
}
}
3. 自定义局部过滤器工厂(GatewayFilterFactory)------ 贴身保镖
针对特定路由生效。比如只检查 /admin/** 的登录状态。
- 核心接口 :
AbstractGatewayFilterFactory
java
package com.haogu.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author ShanYu
* @version 1.0
* @description: 自定义局部过滤器
*
*/
@Component
public class LoginGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(token == null){
Map<String, Object> result = new HashMap<>();
result.put("status", 401);
result.put("msg", "未登录-局部");
return response(exchange.getResponse(), result);
}
return chain.filter(exchange);//放行
}
};
}
/**
* 往前端返回json串
* @param response
* @param data
* @return
*/
private Mono<Void> response(ServerHttpResponse response, Object data) {
String jsonData = null;
try {
jsonData = new ObjectMapper().writeValueAsString(data);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer buffer =
response.bufferFactory().wrap(jsonData.getBytes(StandardCharsets.UTF_8));
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
五、总结与避坑指南
- 响应式编程 :Gateway基于WebFlux,所以你会看到很多
Mono、Flux。如果你习惯了Servlet的同步编程,刚开始会觉得脑子转不过弯。记住:return chain.filter(exchange)是把接力棒交给下一个人。 - 依赖冲突 :再次强调,
pom.xml里别手贱加spring-boot-starter-web,除非你想看控制台报错报错。 - Nacos联动 :
lb://服务名是灵魂,一定要确保Nacos里能查到这个服务,否则网关会报404 Not Found或者Service Unavailable。 - 过滤器顺序 :全局过滤器和局部过滤器是可以共存的,执行顺序由
getOrder()决定。