一、GateWay的介绍
1、为什么要使用网关
在springcloud有多个微服务,首先要考虑的是不是如果不配置网关各个服务的端口都暴露在外?还有过滤器和鉴权功能是不是在每个服务都配置一遍?所以网关主要作用是作为项目的统一入口
2、GateWay的特性
- Spring WebFlux 基于 Reactor响应式框架,基于Netty通讯框架是NIO(同步非阻塞式IO)。
- Gateway 提供了统一的路由方式(反向代理)
- 为微服务架构提供种简单有效的统的API路由管理式,提供了Spring Cloud Discovery Client(如Eureka、Nacos)、Ribbon、Hystrix等组件的集成方案
3、核心内容
- 路由(Route):一个 Route 由路由 ID,转发目标URI,多个断言(Predicates) 以及多个过滤器 (Filters )构成。Gateway 上可以配置多个 Routes。处理请求时会按优先级排序,找到第一个满足所有 Predicates 的 Route;
- 断言(Predicate):SpringCloud Gateway 中内置了许多断言工厂,通过断言匹配http请求中的任何内容(请求头、请求参数等),如果匹配成功,则匹配断言所在路由。
- 过滤器(Filter):在请求前后执行业务逻辑,可以设置局部过滤和全局过滤。比如鉴权、日志监控、流量控制、修改请求头、修改响应等。
常用的局部过滤器说明:
过滤器前缀 | 作用 | 参数 |
---|---|---|
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径数量 |
AddRequestHeader | 为原始请求添加 Header | Header 的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
Retry | 针对不同的响应进行重试 | reties、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小 | 请求包大小,单位字节,默认5M |
SetPath | 修改原始请求的路径 | 修改后的路径 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
RequestRateLimiter | 对请求限流,限流算法为令牌桶 | KeyResolver、reteLimiter、statusCode、denyEmptyKe |
常用的全局过滤器
过滤器名称 | 作用 |
---|---|
ForwardPathFilter / ForwardRoutingFilter | 路径转发相关过滤器 |
LoadBalanceerClientFilter | 负载均衡客户端相关过滤器 |
NettyRoutingFilter / NettyWriteResponseFilter | Http 客户端相关过滤器 |
RouteToRequestUrlFilter | 路由 URL 相关过滤器 |
WebClientHttpRoutingFilter / WebClientWriteResponseFilter | 请求 WebClient 客户端转发请求真实的URL并将响应写入到当前的请求响应中 |
WebsocketRoutingFilter | websocket 相关过滤器 |
4、核心流程图
三、简单网关搭建
我的环境是springcloud.alibaba.2.2.6.RELEASE,以nacos为注册中心
要测试网关所以需要至少需要两个服务一个网关server、一个业务服务nacos-order,当然在企业级项目中还会有很多微服务,比如oauth服务端等
1、 创建业务服务order-server 端口配置8082
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
创建简单的OrderController用于测试web请求
@RestController
@RequestMapping("order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
@RequestMapping("/info")
public String info() {
logger.info("/order/info==========================》");
return "order no 100";
}
}
2、 创建GateWay网关服务 端口8099
pom依赖
<!-- 网关 -->
<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>
yml配置
server:
port: 8099
servlet:
context-path: /
spring:
cloud:
gateway:
#default-filters:我们可以方便的使用default-filters,在请求中加入一个自定义的header,我们加入一个KV为gateway-env:springcloud-gateway,来注明我们这个请求经过了此网关。这样做的好处是后续服务端也能够看到。
default-filters:
- AddRequestHeader=gateway-env, springcloud-gateway
# 配置路由,可以配置多个
routes:
- id: "order-server" # id 自定义路由的id
uri: "http://127.0.0.1:8082" # uri就是 目标服务地址
predicates:# 断言,也就是路由条件 ,这里使用了path作为路由条件
- Path=/order-server/** #配合StripPrefix使用 如果不截取会造成转发路径多个/order http://localhost:8099/order/order/get
filters:
- PreserveHostHeader # 防止host被修改为localhost
- StripPrefix=1 #过滤器StripPrefix,作用是去掉请求路径的最前面n个部分截取掉。
#当上游的请求,进入了Hystrix熔断降级机制时,就会调用fallbackUri配置的降级地址
- name: Hystrix
args:
name: fallbackCmdOrder
fallbackUri: forward:/fallbackOrder
# 配置了CustomRequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:如果无需处理限流响应可使用RequestRateLimiter 返回429
- name: CustomRequestRateLimiter
args:
#具体配置ResolverConfig 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
key-resolver: '#{@apiKeyResolver}'
#r令牌桶每秒填充平均速率。
redis-rate-limiter.replenishRate: 1
#令牌桶总容量。
redis-rate-limiter.burstCapacity: 3
#网关统一配置跨域请求
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- GET
- POST
- DELETE
- PUT
- OPTION
3、测试路由(routes)
yml相关配置如下
1)启动nacos-order和gateway-server
2)访问:http://localhost:8099/order-server/order/info
注意的是order-server为断言(predicates)条件,断言都返回真,才会真正执行预设定的路由
3)成功返回
4、测试Hystrix降级
yml相关配置
1)GateWay网关服务创建降级处理类FallbackController
在多服务情况下可以根据实际情况配置fallbackUri: forward:/fallbackOrder
package com.na.fallback;
import com.na.base.BaseResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author kele
* <p>
* 网关统一降级
*/
@RestController
public class FallbackController {
/**
* 如果想针对mothed方式进行降级返回 可将@RequestMapping改成对应的post、get等
* @return
*/
@RequestMapping("/fallbackOrder")
public BaseResponse fallbackOrder() {
BaseResponse response = new BaseResponse();
response.setCode(100);
response.setMessage("服务暂时不可用");
return response;
}
}
2)启动gateway-server,关闭nacos-order
3) 可以看到返回的信息,证明降级接口/fallbackOrder成功触发了
5、测试限流
限流使用是的令牌桶算法,方便测试限流,令牌生成速率及总量设置得相对较低,生产环境当然要根据实际情况设置
yml相关配置
1)创建CustomRequestRateLimiterGatewayFilterFactory处理类,主要是为了被限流的友好的返回
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* RequestRateLimiter限流返回429
* 重写apply()友好返回
*/
@Slf4j
@Component
public class CustomRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
public CustomRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
super(defaultRateLimiter, defaultKeyResolver);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
@Override
public GatewayFilter apply(Config config) {
KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);
return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {
// if (EMPTY_KEY.equals(key)) {
// if (denyEmpty) {
// setResponseStatus(exchange, emptyKeyStatus);
// return exchange.getResponse().setComplete();
// }
// return chain.filter(exchange);
// }
String routeId = config.getRouteId();
if (routeId == null) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
routeId = route.getId();
}
String finalRouteId = routeId;
return limiter.isAllowed(routeId, key).flatMap(response -> {
for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
}
if (response.isAllowed()) {
return chain.filter(exchange);
}
log.warn("已限流: {}", finalRouteId);
ServerHttpResponse httpResponse = exchange.getResponse();
httpResponse.setStatusCode(config.getStatusCode());
if (!httpResponse.getHeaders().containsKey("Content-Type")) {
httpResponse.getHeaders().add("Content-Type", "application/json");
}
DataBuffer buffer = httpResponse.bufferFactory().wrap("{'msg':'访问已受限制,请稍候重试'}".getBytes(StandardCharsets.UTF_8));
return httpResponse.writeWith(Mono.just(buffer));
// return exchange.getResponse().setComplete();
});
});
}
private <T> T getOrDefault(T configValue, T defaultValue) {
return (configValue != null) ? configValue : defaultValue;
}
}
2)创建限流策略配置类ResolverConfig
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
/**
* @Description
* @Author kele
* @Data 2023/8/5 16:37
* 限流策略,只能注入一个 KeyResolver Bean
*/
@Configuration
public class ResolverConfig {
/**
* 这里根据用户ID限流,请求路径中必须携带userId参数
*/
// @Bean
// KeyResolver userKeyResolver() {
// return new KeyResolver() {
// @Override
// public Mono<String> resolve(ServerWebExchange exchange) {
// Mono mono;
// try {
// mono = Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
// } catch (NullPointerException e) {
// throw new RRException("请求路径中必须携带user参数");
// }
// return mono;
// }
// };
// }
/**
* 如果需要根据IP限流 则开启注释
*/
// @Bean
// public KeyResolver ipKeyResolver() {
// return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
// }
/**
* 如果需要根据接口的URI进行限流,则开启注释
*/
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
3)重启nacos-order和gateway-server
4)短时间内进行多次访问,限流也被成功触发了