目录
一、了解GateWay
1、什么是服务网关
传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。那有了网关之后,能够起到怎样的改善呢?
网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,所以,使用网关的好处在于:
(1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;
(2)降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性
(3)解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑
2、服务网关的基本功能
3、流量网关与服务网关的区别
流量网关(如Nignx)是指提供全局性的、与后端业务应用无关的策略,例如 HTTPS证书卸载、Web防火墙、全局流量监控等。
服务网关(如Spring Cloud Gateway)是指与业务紧耦合的、提供单个业务域级别的策略,如服务治理、身份认证等。也就是说,流量网关负责南北向流量调度及安全防护,微服务网关负责东西向流量调度及服务治理。
主流网关的对比与选型
(1)Kong 网关:Kong 的性能非常好,非常适合做流量网关,但是对于复杂系统不建议业务网关用 Kong,主要是工程性方面的考虑
(2)Zuul1.x 网关:Zuul 1.0 的落地经验丰富,但是性能差、基于同步阻塞IO,适合中小架构,不适合并发流量高的场景,因为容易产生线程耗尽,导致请求被拒绝的情况
(3)gateway 网关:功能强大丰富,性能好,官方基准测试 RPS (每秒请求数)是Zuul的1.6倍,能与 SpringCloud 生态很好兼容,单从流式编程+支持异步上也足以让开发者选择它了。
(4)Zuul 2.x:性能与 gateway 差不多,基于非阻塞的,支持长连接,但 SpringCloud 没有集成 zuul2 的计划,并且 Netflix 相关组件都宣布进入维护期,前景未知。
综上,gateway 网关更加适合 SpringCloud 项目,而从发展趋势上看,gateway 替代 zuul 也是必然的。
二、GateWay初体验
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.chensir</groupId>
<artifactId>spring-cloud-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-cloud-gateway</artifactId>
<dependencies>
<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>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
配置文件
server:
port: 8083
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: rout01
uri: http://httpbin.org:80/
predicates: # 断言
- Path=/a/** # 断言请求的url二级是否以a开头,若是以a开头就转发到上方uri(http://httpbin.org:80/)路径 比如:请求前端请求为http://localhost:8080/a/login,由于二级路径为a所以会转发到http://httpbin.org:80/a/login
filters:
- StripPrefix=1 # 转发后是否去除path前缀,默认为0不去除前缀 1去除前缀. 比如此示例前缀为Path=/a/** 若是去除前缀转发后就为http://httpbin.org:80/login
- AddRequestParameter=red, blue # 在请求参数上会加上 color = blue key为color,value为blue
- AddResponseHeader=name,zhangbozhi # 响应头添加信息
- AddRequestHeader=sign,123 # 请求头添加信息
- id: rout02
uri: https://blog.csdn.net/weixin_45326523
predicates:
- Path=/b/**
filters:
- StripPrefix=1
# 全局
default-filters:
- AddRequestHeader=sign,123 # 添加请求头
- AddResponseHeader=company,206
- AddRequestHeader=traceid,5nifaiea8787808877
# 此配置文件并不实用(比如想在每个接口都加个traceid并且值不唯一,再使用此配置文件就达不到需求了),通常我们都是去定制化局部过滤器与全局过滤器 可见 三和四
在配置文件中路由route主要由id、目标uri、断言和过滤器集合组成。
(1)id:路由标识,要求唯一,名称任意(默认值uuid,一般不用,需要自己定义)
(2)uri:请求最终被转发到的目标地址
(3)order:路由优先级,数字越小,优先级越高
(4)predicates:断言数组(支持多个断言,如有多个需要都符合断言条件),即判断条件;如果返回值是true,则转发请求到uri属性指定的服务中。
(5)filters:过滤器数组,在请求传递过程中,对请求做一些修改
在此示例中如果请求url为http://localhost:8083/a/... 就会转发到 http://httpbin.org:80/上并且不携带二级目录a(因为StripPrefix=1),如果请求url为http://localhost:8083/b/... 就会转发到 https://blog.csdn.net/weixin_45326523 上并且不携带二级目录b。
三、定制化全局过滤器GlobalFilter
配置文件中把全局的过滤去掉,pom文件不变;
CustomGlobalFilter
//全局过滤器
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override //exchange 交换机
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//通过交换机获取的request只能读 不能写
ServerHttpRequest request = exchange.getRequest();
//需要mutate下 就可以操作request
ServerHttpRequest request2 = request.mutate().header("traceid", IdUtil.fastUUID()).build();
// exchange.mutate().request(request2).build() 把新的放进去,chain.filter链式调用
return chain.filter(exchange.mutate().request(request2).build());
}
@Override
public int getOrder() {
//路由优先级,数字越小,优先级越高
return -1;
}
}
config配置(bean加载)
@Configuration
public class FilterConfig {
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
}
这样就可以对gateway网关进行全局定制化!
四、局部过滤器GatewayFilter
ElapsedTimeGatewayFilter
//记录接口耗时
public class ElapsedTimeGatewayFilter implements GatewayFilter, Ordered {
private final static String BEAGIN = "begin";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(BEAGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
long startTime = exchange.getAttribute(BEAGIN);
long endTime = System.currentTimeMillis();
String url = exchange.getRequest().getURI().getRawPath();
System.out.println(StrUtil.format("{}耗时:{}", url, endTime - startTime));
})
);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
该过滤器实现的GatewayFilter接口,而全局过滤器实现的是GlobalFilter接口。
而实现的接口结构都一样 不同的是bean的加载方式不同 等。
config配置
@Component
public class ElapsedTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new ElapsedTimeGatewayFilter();
}
}
至此 局部过滤器并不能生效,因为还没指定哪个路由标识(id)去使用他。因此需要在配置文件中加上 ElapsedTime;
配置文件
server:
port: 8083
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: rout01
uri: http://localhost:9090/
predicates: # 断言
- Path=/a/** # 断言请求的url二级是否以a开头,若是以a开头就转发到上方uri(http://httpbin.org:80/)路径 比如:请求前端请求为http://localhost:8080/a/login,由于二级路径为a所以会转发到http://httpbin.org:80/a/login
filters:
- StripPrefix=1 # 转发后是否去除path前缀,默认为0不去除前缀 1去除前缀. 比如此示例前缀为Path=/a/** 若是去除前缀转发后就为http://httpbin.org:80/login
- AddRequestParameter=red, blue # 在请求参数上会加上 color = blue key为color,value为blue
- AddResponseHeader=name,zhangbozhi # 响应头添加信息
- AddRequestHeader=sign,123 # 请求头添加信息
- ElapsedTime # 配合局部过滤器使用
- id: rout02
uri: https://blog.csdn.net/weixin_45326523
predicates:
- Path=/b/**
filters:
- StripPrefix=1
加上了 -ElapsedTime 这样在使用rout01标识时才会走局部过滤器逻辑!
五、自定义全局过滤器验证token
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
private static final String TOKEN_SECRET ="chensir123";
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
@Override
public Mono<Void> CustomGlobalFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
List<String> tokenList = request.getHeaders().get("Authorization");
//校验是否为空
if (ObjectUtil.isEmpty(tokenList)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
String token = tokenList.get(0);
//校验是否正确(合法)
boolean isTrue = false;
try {
isTrue = JWTUtil.verify(token, TOKEN_SECRET.getBytes());
} catch (Exception ex){
ex.printStackTrace();
}
if (isTrue == false) {
//设置http响应状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);//401 未认证
return response.setComplete();
}
//走到这个位置,说明token是正确合法的
final JWT jwt = JWTUtil.parseToken(token);
System.out.println(jwt.getPayload().toString());
UserInfo userInfo = JSONUtil.toBean(jwt.getPayload().toString(), UserInfo.class);
Integer userId = userInfo.getUserId();
//复制一份原来的request,request2
ServerHttpRequest request2 = request.mutate().header("userId", String.valueOf(userId)).build();
//证明token验证成功,可以正常调用后方的具体接口了
// chain.filter(exchange)
//复制一份交换机,把我们上一步新建的request2 放进去,之后继续执行.
return chain.filter(exchange.mutate().request(request2).build());
}
@Override
public int getOrder() {
return -1;
}
}