SpringCloud Gateway路由网关
网关的实质就是过滤器的组合
一、微服务网关的好处
二、SpringCloud Gateway简介
Gateway网关是我们服务的守门神,所有前端访问微服务的统一入口。Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
在SpringCloudGateway之前,SpringCloud并不自己开发网关,而是使用Netflix公司的Zuul框架,不过zuul2.0更新迭代缓慢,难以满足Spring的更新需求。于是就有了SpringCloudGateway。其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
核心功能特性:
路由:gateway加入后,一切请求都必须先经过gateway,因此gateway就必须根据某种规则,把请求转发到某个微服务,这个过程叫做路由。
权限控制:请求经过路由时,我们可以判断请求者是否有请求资格,如果没有则进行拦截。
限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
**网关的实质:**网关中的每一个功能都是由过滤器链完成的。
三、快速入门
首先,我们来研究下Gateway的路由功能,基本步骤如下:
- 创建SpringBoot工程gateway_server,引入网关依赖
- 编写启动类
- 编写基础配置:服务端口,应用名称
- 编写路由规则
- 启动网关服务进行测试
1)创建一个gateway_server的工程
在工程下添加依赖
<?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>cloud-demo</artifactId>
<groupId>com.itheima.sh.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)编写启动类
package com.itheima.sh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @program: eureka-ribbon
* @description:
* @author: Mr.Wang
* @create: 2020-11-28 07:32
**/
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args){
SpringApplication.run(GatewayApplication.class, args);
}
}
3)编写配置
server:
port: 10010 #服务端口
spring:
application:
name: gateway-server #指定服务名
4)编写路由规则
server:
port: 10010 #服务端口
spring:
application:
name: gateway-server #指定服务名
cloud:
gateway:
routes:
- id: user-service-route # 当前路由的唯一标识
uri: http://127.0.0.1:8081 # 路由的目标微服务地址
predicates: # 断言 判断 前端浏览器发送url和当前path是否匹配
- Path=/user/** # 按照路径匹配的规则
#这个的话是如果要拦截多个就在下面配置
# - id: order-service-route # 当前路由的唯一标识
# uri: http://127.0.0.1:9081 # 路由的目标微服务地址
# predicates: # 断言 判断 前端浏览器发送url和当前path是否匹配
# - Path=/order/** # 按照路径匹配的规则
5)启动测试
当我们访问:http://localhost:10010/user/itcast,符合`/user/**`的规则,
因此请求被代理到http://localhost:8081/user/itcast
四、面向服务的路由
作为客户端放在Eureka的中
1.添加Eureka客户端依赖
2. 添加Eureka配置
3. 修改映射配置
4. 启动测试
1)在Gateway中添加Eureka客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)添加Eureka配置
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
cloud:
gateway:
routes:
- id: user-service # 当前路由的唯一标识
uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
predicates: # 断言
- Path=/user/** # 按照路径匹配的规则
- 修改映射配置
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
cloud:
gateway:
routes:
- id: user-service # 当前路由的唯一标识
uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
predicates: # 断言
- Path=/user/** # 按照路径匹配的规则
这里修改了uri的路由方式:
- lb:负载均衡的协议,将来会使用Ribbon实现负载均衡
- user-service:服务的id
4) 启动测试
再次访问,也可以达到负载均衡
五、其他路由的方式
可以访问网站 SpringBoot网站网关路由的地址
六、局部过滤器
GatewayFilter Factories是Gateway中的局部过滤器工厂,作用于某个特定路由,允许以某种方式修改传入的HTTP请求或返回的HTTP响应包括下面的一些过滤工厂:
局部过滤器演示
以AddRequestHeader GatewayFilter Factory 这个过滤器举例子
1)在原有的基础上添加请求头
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
cloud:
gateway:
routes:
- id: user-service # 当前路由的唯一标识
uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
predicates: # 断言
- Path=/user/** # 按照路径匹配的规则
filters: # 过滤项
- AddRequestHeader=name,bocai
其中:
filters
:就是当前路由规则的所有过滤器配置AddRequestHeader
是添加一个头信息 name就是头 bocai是头里的信息
2)查看结果
Hystrix支持(路由的降级)
hystrix支持步骤:
1)引入Hystrix的依赖
2)定义降级的处理函数
3)定义降级处理规则
4)启动测试
1)引入Hystrix的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2)定义降级的处理函数 出现错误跳转的类上
package com.itheima.sh.gateway.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @program: eureka-ribbon
* @description: 路由中定义降级的类
* @author: Mr.Wang
* @create: 2020-11-28 08:36
**/
@RestController
public class FallbackController {
@RequestMapping(value = "fallbackTest")
public Map<String,String> fallbackController(){
HashMap<String, String> map = new HashMap<>();
map.put("code", "502");
map.put("信息", "请重新连接");
return map;
}
}
3)定义降级处理规则 可以通过default-filter来配置,会作用于所有的路由规则。
server:
port: 10010 #服务端口
spring:
application:
name: gateway-server #指定服务名
cloud:
gateway:
routes:
- id: user-service # 当前路由的唯一标识
uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
# uri: http://127.0.0.1:8081 # 路由的目标微服务地址
predicates: # 断言
- Path=/user/** # 按照路径匹配的规则
filters: # 过滤项
- AddRequestHeader=name,itheima
default-filters: # 默认过滤项
- name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/fallbackTest # 失败后的跳转路径
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 1000 # 失败超时时长
eureka: #这里的话是将gateway放在eureka服务中
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
default-filters:默认过滤项,作用于所有的路由规则
- name:过滤工厂名称,这里指定Hystrix,意思是配置Hystrix类型
- args:配置过滤工厂的配置
- name:Hystrix的指令名称,用于配置例如超时时长等信息
- fallbackUri:失败降级时的跳转路径 fallbackTest 是自己定义的错误跳转页面
4)启动测试
再UserController的业务中打断点,让请求超时,查看页面的效果:
七、路由前缀
我们之前每次访问的时候 每次都会拦截 /user/**的路径但是我此时有另外一个业务的话不包含user这个路径的话,此时就不会被访问到
此时我们只需要在GateWay_server端加个配置: - StripPrefix=1
spring:
application:
name: gateway-server #指定服务名
redis:
host: 192.168.200.150
cloud:
gateway:
routes:
- id: user-service-route # 当前路由的唯一标识
# uri: http://127.0.0.1:8081 # 路由的目标微服务地址 这个是一个服务至于一个实例的时候配置的
uri: lb://user-service # 路由的目标微服务, lb:代表负载均衡,user-service:代表服务id 可以把user-service 中的所有实例映射上去
predicates: # 断言 判断 前端浏览器发送url和当前path是否匹配
- Path=/user/** # 按照路径匹配的规则 通过user访问的都会被拦截i
filters: #局部过滤工厂 下面匹配过滤项
- AddRequestHeader=name,itheima #添加请求头的过滤信息
- StripPrefix=1 # 此时会无视user-bocai 这个主要是可以路由到多个微服务上,地址不会被写死
- StripPrefix=1 他可以无视我定义的user,但是user有能被检测到,后面就可以跟我们需要访问的路径了
八、全局过滤器
全局过滤器的顶级接口是:GlobalFilter
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
// 参数: GatewayFilterChain 过滤器链,用于放行请求到下一个过滤器
实现接口,就要实现其中的filter方法,在方法内部完成过滤逻辑,其中的参数包括:
通过添加@Order注解,可以控制过滤器的优先级,从而决定了过滤器的执行顺序。
另外,一个过滤器的执行包括
"pre"
和"post"
两个过程。在GlobalFilter.filter()方法中编写的逻辑属于pre阶段,在使用GatewayFilterChain.filter().then()的阶段,属于Post阶段。优先级最高的过滤器,会在pre过程的第一个执行,在post过程的最后一个执行,如图:
我们可以在pre阶段做很多事情,诸如:
- 登录状态判断
- 权限校验
- 请求限流等
自定义全局过滤器
定义过滤器只需要实现GlobalFilter即可,不过我们有多种方式来完成:
- 方式一:定义过滤器类,实现接口
- 方式二:通过@Configuration类结合lambda表达式
登录拦截案例
现在,我们通过自定义过滤器,模拟一个登录校验功能,逻辑非常简单:
- 获取用户请求参数中的 token 参数
- 判断是否为"admin"
1)写一个类实现 GlobFilter
package com.itheima.sh.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Description:
* @Version: V1.0
*/
@Component
//@Order(1)
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1 获取Request对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 2 获取请求参数中是否包含token
String token = request.getQueryParams().getFirst("token");
// 3 不包含拦截
if (StringUtils.isEmpty(token)) {
// 未授权
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 4 判断当前的token值是否为 admin
if (!"admin".equals(token)) {
// 4.1 不是 拦截
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
2)访问
localhost:10010/user/itcast?access-token=namin
如果token写错的话就访问不到
九、网关限流
网关除了请求路由、身份验证,还有一个非常重要的作用:请求限流。当系统面对高并发请求时,为了减少对业务处理服务的压力,需要在网关中对请求限流,按照一定的速率放行请求。
通过RateLimitFilter限流过滤器进行限流
常见的限流算法包括:
- 计数器算法
- 漏桶算法
- 令牌桶算法
Gateway中限流实现(令牌通算法)
SpringCloudGateway是采用令牌桶算法,其令牌相关信息记录在redis中,因此我们需要安装redis,并引入Redis相关依赖。
Gateway会在Redis中记录令牌相关信息,我们可以自己定义令牌桶的规则,例如:
- 给不同的请求URI路径设置不同令牌桶
- 给不同的登录用户设置不同令牌桶
- 给不同的请求IP地址设置不同令牌桶
Redis中的一个Key和Value对就是一个令牌桶。因此Key的生成规则就是桶的定义规则。SpringCloudGateway中key的生成规则定义在KeyResolver
接口中:
KeyResolver结构
public interface KeyResolver {
Mono<String> resolve(ServerWebExchange exchange);
}
参数:
- Mono:是一个单元素容器,用来存放令牌桶的key
- ServerWebExchange:上下文对象,可以理解为ServletContext,可以从中获取request、response、cookie等信息
KeyResolver实现类中三种令牌桶生成key的方式
给不同的请求URI路径设置不同令牌桶,示例代码:
return Mono.just(exchange.getRequest().getURI().getPath());// 获取请求URI
给不同的登录用户设置不同令牌桶
return exchange.getPrincipal().map(Principal::getName);// 获取用户
给不同的请求IP地址设置不同令牌桶
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());// 获取请求者IP
实现步骤:
1)导入redis有关的依赖 (不是普通的redis的依赖)
2)配置redis的地址yml文件
3)配置过滤条件key 这里我们选择最后一种,使用IP地址的令牌桶key。
4)配置桶的参数
5)测试
1)导入redis有关的依赖 (不是普通的redis的依赖)
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
注意:这里不是普通的redis依赖,而是响应式的Redis依赖,因为SpringGateway是基于WebFlux的响应式项目。
2)配置redis的地址yml文件
spring:
redis:
host: 192.168.200.129
3)配置过滤条件key (这里我们选择最后一种,使用IP地址的令牌桶key。)
package com.itheima.sh.ratelimit;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Description:
* @Version: V1.0
*/
@Component
public class IpKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
// IP
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
4)配置桶的参数
server:
port: 10010 #服务端口
spring:
application:
name: gateway-server #指定服务名
cloud:
gateway:
routes:
- id: user-service # 当前路由的唯一标识
uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
# uri: http://127.0.0.1:8081 # 路由的目标微服务地址
predicates: # 断言
- Path=/user-api/** # 按照路径匹配的规则
filters: # 过滤项
- AddRequestHeader=name,itheima
- StripPrefix=1
default-filters: # 默认过滤项
- name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
args: # 指定过滤的参数
name: fallbackcmd # hystrix的指令名
fallbackUri: forward:/fallbackTest # 失败后的跳转路径
- name: RequestRateLimiter #请求数限流 名字不能随便写
args:
key-resolver: "#{@ipKeyResolver}" # 指定一个key生成器 ipKeyResolver是自己定义的key生成类的首字母小写
redis-rate-limiter.replenishRate: 2 # 生成令牌的速率
redis-rate-limiter.burstCapacity: 2 # 桶的容量
redis:
host: 192.168.200.129
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 1000 # 失败超时时长
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
这里配置了一个过滤器:RequestRateLimiter,并设置了三个参数:
key-resolver
:"#{@ipKeyResolver}"
是SpEL表达式,写法是#{@bean的名称}
,ipKeyResolver就是我们定义的Bean名称redis-rate-limiter.replenishRate
:每秒钟生成令牌的速率redis-rate-limiter.burstCapacity
:令牌桶的容量
这样的限流配置可以达成的效果:
- 每一个IP地址,每秒钟最多发起2次请求
- 每秒钟超过2次请求,则返回429的异常状态码
5)测试
我们快速在浏览器多次访问http://localhost:10010/user/user/itcast,就会得到一个错误:429错误
十、解决跨域问题
有时候,我们需要对所有微服务跨域请求进行处理,则可以在gateway中进行跨域支持。修改application.yml,添加如下代码:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE