Spring之Gateway网关
gateway
前言
什么是网关?简单理解就是我们所有服务的入口,当我们使用了微服务以后,每个服务都会有一个对应的接口,比如我们有用户服务,订单服务等等,如果没有网关的话,那么前端是这样调用的
很明显app和h5需要知道所有微服务的地址,显然会让前端变得很复杂,同时也不太安全,那如果有网关后是怎么样的呢?如下
这样一来,所有流量就会从网关进来了,当然了网关会存在单点故障问题,这个可以通过负载均衡就可以解决了
网关核心概念
路由:路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。通过ID使用微服务名称,因为微服务一般就是全局统一的
断言:简单了解就是对参数、请求头,URI等的判断
Filter:拦截器,对请求进行拦截处理等,跟mvc的拦截器差不多
网关工作原理
网关的使用
spring网关的官方api文档:spring gateway
网关的使用其实非常简单,只需要做以下的两步即可
1. 引入maven依赖
<!-- 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. 配置yml文件
server: port: 9999 spring: application: name: gateway #配置nacos注册中心地址 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: user_route #路由ID,全局唯一,建议配置服务名 uri: lb://user #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/user/** # 断言,路径相匹配的进行路由
3. 定义springBoot配置类启动即可使用了
网关使用遇到的问题
首先,你在使用网关的时候需要排除webmvc,因为网关是基于webflux实现的,所以必须排除这个,否则启动不了
<!-- gateway网关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </exclusion> </exclusions> </dependency>
其次,不知道为啥我启动的时候报下面的错
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.buildConnectionProvider(GatewayAutoConfiguration.java:798)
The following method did not exist:
reactor.netty.resources.ConnectionProvider$Builder.evictInBackground(Ljava/time/Duration;)Lreactor/netty/resources/ConnectionProvider$ConnectionPoolSpec;
The method's class, reactor.netty.resources.ConnectionProvider$Builder, is available from the following locations:
jar:file:/E:/javaEvn/mvn-need/io/projectreactor/netty/reactor-netty/0.9.10.RELEASE/reactor-netty-0.9.10.RELEASE.jar!/reactor/netty/resources/ConnectionProvider$Builder.class
The class hierarchy was loaded from the following locations:
reactor.netty.resources.ConnectionProvider.Builder: file:/E:/javaEvn/mvn-need/io/projectreactor/netty/reactor-netty/0.9.10.RELEASE/reactor-netty-0.9.10.RELEASE.jar
reactor.netty.resources.ConnectionProvider.ConnectionPoolSpec: file:/E:/javaEvn/mvn-need/io/projectreactor/netty/reactor-netty/0.9.10.RELEASE/reactor-netty-0.9.10.RELEASE.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of reactor.netty.resources.ConnectionProvider$Builder
Process finished with exit code 1
看报错信息就是说找不到类之类的,所以尝试了几个解决方案
- 删除maven对应的依赖,因为有时可能因为网络问题引起jar包有问题,但是无法解决
- 看网上说可能是版本不匹配的问题,官网的推荐是版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub,
- 但是我的版本也是没问题,都是匹配的
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
最后实在没办法了,找到了网关启动类GatewayAutoConfiguration,里面有个方法叫org.springframework.cloud.gateway.config.GatewayAutoConfiguration.NettyConfiguration#buildConnectionProvider的builder.evictInBackground(pool.getEvictionInterval());报错了,这也是上面报错的原因,所以就尝试了一下对reactor.netty.resources.ConnectionProvider依赖进行了升级,如下
<dependency> <groupId>io.projectreactor.netty</groupId> <artifactId>reactor-netty</artifactId> <version>0.9.25.RELEASE</version> </dependency>
这样一来,就可以解决了,但是官网推荐的版本匹配岂不是有问题吗,这个有待于后续的研究
这个问题解决以后,gateway就可以正常启动了
断言工具
我们常用的是Path断言,断言就是个判断
predicates: - Path=/user/** # 断言,路径相匹配的进行路由
其他更多的可以查看官网:spring 断言类
如果提供的不够还可以进行自定义,找一个参考就行了,命令也是要按照规范来即可
GatewayFilter拦截链
网关提供了GatewayFilter接口,默认提供了很多实现,当然我们也可以自己进行实现,然后放到容器中,他就会走我们的逻辑了,比如我定义了一个拦截器的实现,如下
@Component
public class ZxcGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String name = config.getName();
String value = config.getValue();
System.out.println(name);
System.out.println(value);
return chain.filter(exchange);
}
};
}
}
注意:名字要规范ZxcGatewayFilterFactory,Zxc为你使用的名字,后面的格式是固定的,然后放到ioc容器中就可以了,然后是在yml配置中进行配置,如下
gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: user_route #路由ID,全局唯一,建议配置服务名 uri: lb://user #lb 整合负载均衡器ribbon,loadbalancer filters: - Zxc=zxc,ttt predicates: - Path=/user/** # 断言,路径相匹配的进行路由
spring提供了多个GatewayFilterFactory,地址:Spring GatewayFilterFactory
配置比较简单,不过这种是局部配置的,我们一般会使用全局的,如下
GlobalFilter
这个是全局的,spring一样提供了很多,地址:Spring GlobalFilter
如果这些还不够,我们可以自己再提供一些实现,如下面定义了校验权限和校验白名单的类
@Component
public class AuthGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
ServerHttpResponse response = exchange.getResponse();
if(token == null) {
return response.writeWith(Mono.just(response.bufferFactory().wrap("没有权限".getBytes())));
}
return chain.filter(exchange);
}
}
package com.zxc.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class WhiteGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//todo 校验白名单逻辑
return chain.filter(exchange);
}
}
然后只要把实现类放到ioc容器中即可,以上都是网关提供的一些扩展点
适配器模式
网关提供的Filter过滤器包含了两种,一种是局部的GatewayFilter,一种是全局的GlobalFilter,先看看下面的两种接口的定义
public interface GatewayFilter extends ShortcutConfigurable {
/**
* Name key.
*/
String NAME_KEY = "name";
/**
* Value key.
*/
String VALUE_KEY = "value";
/**
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
public interface GlobalFilter {
/**
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
现在有个问题是说,比如我需要把两种拦截器都放在一个列表里,但是他们是不同接口类型的,那么我可以怎么做?这时候就可以使用适配器了,这是个很巧妙的设计,在源码中有这么一个适配器的类,如下
private static class GatewayFilterAdapter implements GatewayFilter {
private final GlobalFilter delegate;
GatewayFilterAdapter(GlobalFilter delegate) {
this.delegate = delegate;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return this.delegate.filter(exchange, chain);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");
sb.append("delegate=").append(delegate);
sb.append('}');
return sb.toString();
}
}
是不是很巧妙,把GlobalFilter通过GatewayFilterAdapter包装一下,就可以变成了GatewayFilter类型的,这样一来两种Filter就可以放在同一个list里面的,设计核心点:
1. GatewayFilterAdapter包含了一个GlobalFilter的对象
2. GlobalFilter实现了GatewayFilter接口
3. 在GatewayFilterAdapter实现GatewayFilter的接口使用内部的GlobalFilter来进行实现
通过以上的设计,就间接把GlobalFilter和GatewayFilter整合在一起了,很值得我们去学习
网关限流整合
官网提供的
基于redis+lua脚本方式采用令牌桶算法实现了限流
具体文档为:gateway基于 redis+lua 限流
整合sentinel限流
参考地址
更多推荐
所有评论(0)