SpringCloudGateWay
SpringCloudGateWay
概述
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
简单来说:SpringCloud Gateway是使用WebFlux 中的reactor-netty 响应式编程组件,底层使用了Netty通讯框架,速度比Zuul 1.0更快。
主要功能
- 反向代理
- 权限控制
- 流量控制
- 熔断
- 日志监控等
特点
- 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0
- 能够匹配任何请求属性的routes。
- Predicates和filter组合成为特定路由。
- 断路器集成。
- Spring Cloud DiscoveryClient 集成
- 编写Predicates和filter容易
- 限流
- 路径重写
filter(过滤器):
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
Predicate(断言):
参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
route(路由):
是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
原理
客户端向Spring-Gateway发送请求,如果网关的处理程序映射(Gateway Handler Mapping)确定请求与路由匹配,则将其转发到Web网关处理程序(Gateway Web Handler),在通过特定的处理请求的过滤器链运行请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“预”过滤器逻辑。然后进行代理请求。
GateWay 服务搭建
1、搭建SpringCloud服务中心 参考:SpringCloud 框架搭建
2、创建网关服务中心:在刚刚搭建的框架中搭建网关
-
创建项目 SpringCloud-Gateway-9527
-
导入Pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloud-Way</artifactId> <groupId>com.wyx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>SpringCloud-way-9527</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.tar>8</maven.compiler.tar> </properties> <dependencies> <!--Spring-Way 启动依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--连接 consul 的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!--spring-boot-web 模块 常用的3个 网关不需要,否则启动不了--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- <dependency>--> <!-- <groupId>org.springframework.boot</groupId>--> <!-- <artifactId>spring-boot-starter-web</artifactId>--> <!-- </dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--热部署插件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!--测试插件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <scope>test</scope> </dependency> <!--lombok 依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> </project>
-
创建application.yaml
server: port: 9527 spring: application: name: cloud-gateway cloud: consul: # 服务的主机 host: localhost # 服务的端口号 port: 8500 discovery: # 注册到服务中心的名称 service-name: ${spring.application.name}
-
主启动类
package com.wyx.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class SpringCloudWayMain9527 { public static void main(String[] args) { SpringApplication.run(SpringCloudWayMain9527.class,args); } }
-
启动服务时需要先启动Consul,因为使用的时Consul注册中心 如何启动参考
启动后访问Consul的服务中心查看是否注册完成,访问:http://localhost:8500/
搭建服务提供者 8001 - 8002搭建流程参考:https://www.cnblogs.com/Rampant/p/14775726.html 的Consul服务治理,服务提供者注册到Consul。不在具体概述。
配置路由协议:在application.yaml中配置
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
添加配置前:需要访问 http://localhost:8001//1
添加网关配置后:访问:http://localhost:9527//1
这里网关的作用:相当于,在访问的路径上面添加一层,方便管理多个项目。简单来说就是将多个项目提供的方法添加到网关的方法。
第二种配置方法:配置类编写
package com.wyx.cloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WayConfig {
/*
* route-8002 对应id
* //** 对应 -Path
* http://localhost:8002 对应 url
*/
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("route-8002",
r -> r.path("//**")
.uri("http://localhost:8002")
).build();
return routes.build();
}
}
配置类的优先级高于 application.yaml 的优先级
动态路由配置
在上面的配置中,如下
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
我们可以看出,url的值是固定写死的,但是在实际开发中,一个服务一般情况下都是集群的,写死的话不满足要写,于是我们可以使用动态路由配置来实现。
配置如下
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
# lb代表启用负载均衡,cloud-provider-way 代表提供的服务名,配置后当服务提供者集群时,就会轮巡的调用
断言
Predicate 的使用
After
在XXX时间后生效
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
上面的时间是如何获取的?
import java.time.ZonedDateTime;
public class Time {
public static void main(String[] args) {
ZonedDateTime zbj = ZonedDateTime.now();
System.out.println(zbj);
}
}
// 2021-07-16T14:40:35.302+08:00[Asia/Shanghai]
Before
在XXX时间前生效
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
Between
在XXX时间到XXX时间生效
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie
在Cookie满足的条件下生效
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- Cookie=username, wyx
前一个是代表键名,后一个代表值,可以使用Java正则来替换值
Header
在请求头包含的参数满足下生效
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- Header=X-Request-Id, \d+
前一个是代表请求头的键名,后一个代表值,可以使用Java正则来替换值
Host
满足请求主机的信息放行
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- Host=**.somehost.org,**.anotherhost.org
Method
请求方法满足放行
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- Method=,POST
Path
匹配请求路径,路径符合就匹配,前面很多例子都用到了Path不在说明
Query
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
# 如果请求包含green(参数名)查询参数,则前面的路由匹配。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
# 请求的参数名为red 且值满足正则表达式,这里可以匹配 gree或greet 都可以
RemoteAddr
这里运用了计算机网络中的构造超网CIDR ,具体如何匹配请参考计算机网络的书。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
# 192.168.1.1/24 前面24号作为网络号,剩余8为做主机号,可以匹配例如192.168.1.10,具体如何匹配参考计算机网络书
Weight
权重路由匹配转发
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
# 该路由会将约 80% 的流量转发到weighthigh.org,将约 20% 的流量转发到weightlow.org 同一服务的group相同
过滤器
Filter,这里又称路由过滤器(网关过滤器),即HTTP请求通过断言接收过来后在将请求中的参数进行一定的修改,从而达到我们请求的某服务器的参数配置要求。
过滤器主要分为(单一)网关过滤器和全局过滤器,也可以按照时间的先后顺序分为业务逻辑前,和业务逻辑后两种
网关过滤器(GatewayFilter)
网关过滤器,共有31种配置模板
由于在官网中书写例子较多,这里我们随便说明几个,具体使用请查看官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/index.html#gatewayfilter-factories
AddRequestHeader
添加请求头参数,即HTTP请求上添加一个请求头
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
# 请求头名字:X-Request-red 值为blue
SetPath
设置请求路径
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: https://example.org
predicates:
- Path=/red/{segment}
filters:
- SetPath=/{segment}
# 这里的效果是,如果请求为 https://example.org/red/1 ,设置请求路径后为 https://example.org/1
PrefixPath
添加请求前缀
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- PrefixPath=/mypath
# 如果请求的路径为 https://example.org/ 会将其修改为 https://example.org/mypath/
全局过滤器
和网关过滤器功能一样,但是这个是应用在全局,全局过滤器默认也给我们配了许多过滤器,比如ForwardRoutingFilter(转发),ReactiveLoadBalancerClientFilter(负载均衡过滤器) 等等,具体查看官网。
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/index.html#global-filters
转发过滤器:
配置如下:
spring:
cloud:
gateway:
routes:
- id: myRoute
uri: foresrd:///service
predicates:
- Path=/service/**
# 假设请求的是 localhost:8080/index 经过改变后请求的路径为 localhost:8080/service
负载均衡过滤器
spring:
cloud:
gateway:
routes:
- id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-provider-way #匹配后提供服务的路由地址
predicates:
- Path=//** #断言,路径相匹配的进行路由
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
# lb://接微服务名称 可以实现具体的微服务之间的跳转,假设有两个微服务提供者,那么他将轮询的调用微服务提供者
自定义过滤器
虽然在SpringCloudGateway中,存在许多已经配置号的过滤器,但是有时后也不能完全满足我们的需求,于是我们可以自定义过滤器,从而达到我们的需求。
自定义网关过滤器
那么如何才能实现我们自定义的网关过滤器呢?
-
编写过滤器类,需要实现
GlobalFilter
和Ordered
接口package com.wyx.cloud.filter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class CustomizeWayFilter implements Ordered, GatewayFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("进入了自定义网关过滤器"); return chain.filter(exchange); } /* * 控制执行顺序,数值越小优先级越高 * * */ @Override public int Order() { return 0; } }
-
将编写的过滤器注入需要注入的过滤路由
package com.wyx.cloud.config; import com.wyx.cloud.filter.CustomizeWayFilter; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.route.builder.UriSpec; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.function.Function; @Configuration public class WayConfig { /* * route-8002 对应id * //** 对应 -Path * http://localhost:8002 对应 url */ @Bean public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){ RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("route-8002", r -> r.path("//**") .filters(f -> f.filter(new CustomizeWayFilter()) ) .uri("http://localhost:8002") ).build(); return routes.build(); } }
自定义全局过滤器
自定义全局过滤器,和自定义网关过滤器都需要实现 GlobalFilter
和 Ordered
接口,但是全局过滤器,只需要将实现的类当作组件加入SpringIOC容器中即可
如下 ,以下配置需要放到@Configuration下
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
public static class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进入了全局过滤器");
return chain.filter(exchange);
}
@Override
public int Order() {
return -1;
}
}
权限校验
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
public static class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.Request().QueryParams().First("token");
if (token == null){
System.out.println("token id null...");
ServerHttpResponse response = exchange.Response();
response.Headers().add("Content-Type","application/json; charset=utf-8");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
String message = "用户未授权";
DataBuffer wrap = response.bufferFactory().wrap(message.Bytes());
return response.writeWith(Mono.just(wrap));
}
System.out.println("权限校验成功");
return chain.filter(exchange);
}
@Override
public int Order() {
return -1;
}
}
网关限流
为什么要限流:
-
用户增长过快
-
因为某个热点
-
恶意爬虫
-
恶意请求
简单理解就是防止突然间的高流量进入,导致服务器崩溃
限流算法
-
计数器算法
简单来说就是在规定的时间内,限制访问次数,比如一分钟可以访问100次,但是如果有恶意访问在1秒钟就将100次用完,那么后面的其他时间访问也不提供服务,会造成资源浪费,其次就是会使得间接性服务不可用。
-
漏桶算法
就是将请求用队列存储起来,然后对请求的队列慢慢的处理,可以规定多长时间处理多少请求。缺点是当请求过多时,可能会造成队列溢出,或者Java虚拟机内存不够用,从而导致服务故障。
-
令牌漏桶算法
就是在漏桶算法的基础上添加对输入流量的限制,下面我们来看看原理图
大致运行流程如下,用恒定的速度生产令牌,并放入桶(队列)中,如果桶满了,生成的令牌不加入桶中。这时如果收到请求就从令牌桶中获取令牌,如果获取成功就处理请求,否则不处理请求,将请求丢弃或者报错。
Gateway结合redis实现令牌桶算法限流
Spring Cloud GateWay 官方提供了RequestRateLimiterGatewayFliterFactory 使用Redis
和 Lua
脚本实现了令牌桶算法。
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
-
配置application.yaml
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: routes: - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-provider-getway #匹配后提供服务的路由地址 predicates: - Path=/get/** #断言,路径相匹配的进行路由 filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 # 生成令牌速度 单位秒 1秒1个 redis-rate-limiter.burstCapacity: 2 # 桶容量 redis-rate-limiter.requestedTokens: 1 # 每个请求消耗的令牌数量 key-resolver: "#{@pathKeyResolver}" # 使用的规则 redis: timeout: 1000 host: xxx.xxx.xxx.xxx port: 6379 password: xxxx database: 0 lettuce: pool: max-active: 1024 max-wait: 10000 max-idle: 200 min-idle: 5 consul: # 服务的主机 host: localhost # 服务的端口号 port: 8500 discovery: # 注册到服务中心的名称 service-name: ${spring.application.name}
-
限流规则
package com.wyx.cloud.config; 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; @Configuration public class LimitConfig { /* 一次只能使用一种限流规则,即只能注入一个bean 并且需要修改配置文件中的 key-resolver: "#{@pathKeyResolver}" # 使用的规则 其中pathKeyResolver 对应bean的方法名 */ // @Bean public KeyResolver pathKeyResolver(){ // return new KeyResolver() { // @Override // public Mono<String> resolve(ServerWebExchange exchange) { // return Mono.just(exchange.getRequest().getURI().getPath().toString()); // } // }; // 1.8 新特性 return exchange -> Mono.just(exchange.getRequest().getURI().getPath()); } // 参数限流 //@Bean public KeyResolver parameterKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId")); } // IP限流 @Bean public KeyResolver IPKeyResolver(){ return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); } }
高可用网关搭建
参考Nginx的高可用搭建,参考:https://www.cnblogs.com/Rampant/p/14785179.html 的反向代理集群高可用