1.概念
Gateway网关是我们服务的守门神,所有微服务的统一入口。
网关的核心功能特性:路由转发,负载均衡,限流,权限控制
2.使用
1.引入依赖
<!-- gateway 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos 注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.application.yml文件
server:
port: 9000
spring:
application:
name: service-gateway
###nacos注册中心
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# gateway:
# # #路由规则
# routes:
# - id: gateway-product #路由id,唯一
# # uri: http://localhost:8081/ #目标uri,路由到微服务的地址
# uri: lb://service-product # 根据服务名称进行路由 lb:loadbalance 负载均衡
# predicates: # 采用断言的判断条件
# - Path=/product/** # 匹配对应的url请求,将匹配到的请求追加到目标uri的后面 访问/product/** 实际上访问的是 service-product/product/**
# filters: # 网关过滤器
# - name: RequestRateLimiter ##内置过滤器,还可以自定义过滤器和全局过滤器
# args:
## URI限流 超过这个速率会报429错误
# redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
# redis-rate-limiter.burstCapacity: 2 # 令牌桶的上限
# key-resolver: "#{@pathKeyResolver}" # 使用SpEL表达式从Spring容器中获取Bean对象
######第一种方式 gateway 动态路由转发(推荐使用,不用一个服务一个服务地配)
###### 访问的时候加上service名称 eg:localhost:9000/product/save ->
# localhost:9000/service-product/product/save
gateway:
discovery:
locator:
####开启与服务注册中心进行结合,通过serviceid 转发到具体的服务实例
enabled: true #开启基于服务发现的路由规则
lower-case-service-id: true #是否将服务名称转小写
############################################################################
#### 第二种方式 gateway网关 需要一个服务一个服务地配置
# gateway:
# #路由规则
# routes:
# - id: gateway-product #路由id,唯一
## uri: http://localhost:8081/ #目标uri,路由到微服务的地址
# uri: lb://service-product ###根据服务名称进行路由 lb:loadbalance 负载均衡
#
# predicates: # 采用断言的判断条件
# - Path=/product/** # 匹配对应的url请求,将匹配到的请求追加到目标uri之后
# - Query=token #匹配请求参数,请求的参数中带token的就给你路由
# - Method=GET #匹配任意get请求
# - After= 具体时间 #在这个时间之后的请求可以路由 同理还有Before
# - RemoteAddr=127.0.0.1/0 #匹配远程地址请求是RemoteAddr的请求
# - Header=Request-Id # 匹配请求头包含Request-Id 的请求
# gateway 工作原理
# 用户访问带url里面带有 /product/ 就把请求路由到http://localhost:8081/
# 再把后面的path添加上拼接成最终的url访问路径进行访问
# 最终访问路径是 http://localhost:8081/product/**
3.配置过滤器
gateway内置过滤器有31个,常用的有
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量
也可以自己实现GatewayFilter接口自定义过滤器(用的不多),然后把该过滤器添加到配置类里边,例如:
public class CustomGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//TODO 这里面写业务逻辑
System.out.println("自定义网关过滤器被执行");
return chain.filter(exchange); //继续往下执行
}
/**
* 如果有多个过滤器 ,此方法返回的数值越小,执行优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
@Configuration
public class GatewayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r.
//断言,判断条件
path("/product/**")
//目标uri,路由到微服务的地址
.uri("lb://service-product")
//注册自定义网关过滤器
.filters(new CustomGatewayFilter())
//路由id,唯一
.id("product-service"))
.build();
}
}
使用全局过滤器,实现GlobalFilter接口,可以拿到request里面的参数用于权限判断
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行全局过滤器");
ServerHttpRequest request = exchange.getRequest();
//TODO 业务逻辑
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
3.gateway结合sentinel限流
1.增加依赖
<!-- sentinel-gateway 依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
2.application.yml文件
server:
port: 9001
spring:
application:
name: gateway-sentinel
cloud:
sentinel:
filter:
enabled: false #不对网关进行限流
gateway:
#与服务注册中心配合使用,通过service id进行路由转发
discovery:
locator:
lower-case-service-id: true #将服务名称转小写
enabled: true
#路由规则
routes:
- id: service-product
uri: lb://service-product
predicates:
- Path=/product/**
- id: service-order
uri: lb://service-order
predicates:
- Path=/order/**
nacos:
discovery:
server-addr: 127.0.0.1:8848
添加配置类 (分组限流)并自定义限流异常处理器
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 限流异常处理器
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 限流过滤器
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
// By default the order is HIGHEST_PRECEDENCE
return new SentinelGatewayFilter();
}
/**
* 容器初始化时候执行该方法
*/
@PostConstruct
public void doInit() {
//加载网关限流规则
initGatewayRules();
//加载自定义限流异常处理器
initBlockHandler();
}
/**
* 网关限流规则
*/
private void initGatewayRules() {
HashSet<GatewayFlowRule> rules = new HashSet<GatewayFlowRule>();
/**
* resource :资源名称 ,可以是网关中的routes名称/id,也可以时自定义API分组名称
* count :限流阈值
* setIntervalSec:统计时间窗口,单位是秒
*
* 60秒内只能访问3次 超过3次就会报 Blocked by Sentinel: ParamFlowException异常
*/
// rules.add(new GatewayFlowRule("service-product")
// .setCount(3)
// .setIntervalSec(60));
//添加分组限流规则,和 initCustomizedApis()方法一起使用, 不同api限流规则不同。
rules.add(new GatewayFlowRule("product-api")
.setCount(3)
.setIntervalSec(60));
rules.add(new GatewayFlowRule("order-api")
.setCount(5)
.setIntervalSec(60));
GatewayRuleManager.loadRules(rules);
initCustomizedApis();
}
/**
* 自定义限流异常处理器 替代 Blocked by Sentinel: ParamFlowException异常
*/
private void initBlockHandler() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String, String> map = new HashMap<>();
map.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
map.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
}
};
//加载自定义限流异常处理器
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
/**
* 自定义API分组限流 不同的组限流规则不同
*/
private void initCustomizedApis(){
Set<ApiDefinition> definitions = new HashSet<>();
//product-api组
ApiDefinition api1 = new ApiDefinition("product-api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/service-product/product/**") //对product下所有路径进行限流
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
//order-api组
ApiDefinition api2 = new ApiDefinition("order-api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/service-order/order/index")); // 对/order/index/进行限流
}});
definitions.add(api1);
definitions.add(api2);
//加载限流分组
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}