一文带你吃透微服务守门神SpringCloud-GateWay
GateWay简单介绍
一句话:网关是微服务架构系统的守门神 网关本身是一个微服务,是所有微服务的入口。鉴权、限流、路由、监控【监控所有方法执行调用时间】
Gateway和Zuul的对比:
1.gateway是zuul的代替者。并不是zuul有什么大问题 而是后面zuul宣布闭源了 所以springcloud自己做了一个gateway后来就不再集成zuul了
2.Zuul是基于servlet阻塞式的并且不支持任何长连接 gateway是使用Reactor和springboot开发的 使用非阻塞式的API 性能要优于zuul
gateWay的几个核心概念
非常重要!
必须要对这几个概念熟悉了 才能分析他的高级特性以及功能
1. route/路由:路由是网关的最基本组成,由一个路由 id、一个目标地址 url,一组断言工厂及一组
filter组成。若断言为 true,则请求将经由 filter 被路由到目标 url。 一句话理解 就是定义一条游戏规则
2. predicate断言:断言即一个条件判断,根据当前的 http 请求进行指定规则的匹配,比如说 http
请求头,请求时间等。只有当匹配上规则时,断言才为 true,此时请求才会被直接路由到目标地址
(目标服务器),或先路由到某过滤器链,经过过程器链的层层处理后,再路由到相应的目标地址
(目标服务器)。一句话理解 就是定义一个游戏规则中的多个小规则
3. filter过滤器:对请求进行处理的逻辑部分。当请求的断言为 true 时,会被路由到设置好的过滤
器,以对请求进行处理。例如,可以为请求添加一个请求头,或添加一个请求参数,或对请求URI
进行修改等。总之,就是对请求进行处理。 一句话理解 对请求做过滤
入门案例
需求:访问网关服务跳转到百度(静态路由)
1.创建工程并且加入依赖
<?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-alibaba-sxw-parent</artifactId>
<groupId>com.sxw</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>03-gateway-simple</artifactId>
<properties>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- actuator依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<!-- spring-cloud的依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud-alibaba的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.配置文件
server:
port: 9000
spring:
cloud:
gateway:
#添加一组路由
routes:
#路由id 唯一标识/名字
- id: route_to_baidu
#路由到的目标地址
uri: http://www.baidu.com
#断言 也就是规则 我需要定义什么样的请求被被这个路由拦截
predicates:
- Path=/**
3.测试
4.这是使用配置文件配置 也可以使用java类代码来配置
//区别在于可以直接声明好 不需要在配置文件上修改
@SpringBootApplication
public class ApiConfigGateWayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiConfigGateWayApplication.class, args);
}
@Bean
//使用的是构建者模式+java8 lambda的语法来编写 优雅简单
public RouteLocator locator(RouteLocatorBuilder builder){
return builder.routes().route(predicateSpec -> predicateSpec
.path("/**")
.uri("https://www.baidu.com")
.id("api_config_route_to_baidu"))
.build();
}
}
网关层负载均衡
需求:我的服务组成了集群 那么网关支持负载均衡吗? 当然支持
1.pom依赖
小疑问 为什么需要加入注册中心的依赖?
因为网关本身也是一个微服务 那么也可以将他注册到配置中心
负载均衡 顾名思义 用过feign的同学就知道 有点像域名找服务一样
<!-- 注册中心支持 其他依赖省略... 这里使用nacos作注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.配置文件(同样可以使用java配置类的方式进行配置)
这里主要关注这个路由规则
uri这个参数:lb://provider
lb:是网关自有的协议 至于为什么叫lb 因为源码已经写死 感兴趣的可以自己查阅ribbon中的实现
provoder:这个其实就是服务名字 这个服务的集群不管有几个 服务名是不变的
这样就可以通过nacos找到这个名字背后的集群地址 通过某种负载均衡机制找到具体某一台 然后进行请求转发
erver:
port: 9002
spring:
application:
name: sxw-gateway-ribbon
#注册中心配置
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#开启与discoveryClient的整合
gateway:
discovery:
locator:
enable: true
#路由配置
routes:
- id: ribbon_route
uri: lb://provider
predicates:
- Path=/provider/depart/**
3.修改均衡策略 随机负载均衡策略
@Bean
public IRule loadBalanceRule(){
return new RandomRule();
}
4.启动提供者服务provider集群
5.验证
···
http://localhost:9002/provider/depart/get/122
···
gateWay 工作原理
三大组件:高级工作原理图(左) 粗
网关处理器映射器组件 HandlerMapping
网关WEB处理器组件Web Hnadler
网关过滤器组件Filter**
工作流程:
组装网关上下文->循环遍历所有的Mapper获取Handler->匹配路由信息,如果断言失败就返回->进入Filter过滤链条调用链->前置过滤器->自定义过滤器->后置过滤器
网关路由断言工厂(路由规则)
path路由规则:
含义:对这个路径进行拦截
如 - Path=/info/** 请求地址:http://localhost:9000/info/hello 那么就会被path_showinfo_route这个路由拦截转发到http://localhost:8088这个服务
也可以理解 访问 http://localhost:9000/info/hello 等同于访问 http://localhost:8088/info/hello
1.添加两组路由策略
server:
port: 9000
spring:
cloud:
gateway:
#配置断言工厂1 路由到http://localhost:8999 这个服务
routes:
- id: path_consumer_route
uri: http://localhost:8999
predicates:
- Path=/consumer/depart/**
#配置断言工厂2 路由到http://localhost:8088 这个服务
- id: path_showinfo_route
uri: http://localhost:8088
predicates:
- Path=/info/**
2.测试
测试1:http://localhost:9000/consumer/depart/get/1
测试2:http://localhost:9000/info/hello
After路由规则:
一句话 配置的时间要比现在的时间小才能够访问 格式固定
如下:这样是不能访问的
配置文件
server:
port: 9000
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://www.baidu.com
predicates:
- After=2022-01-20T17:42:47.789-07:00[Asia/Shanghai]
其他路由规则
举一反三:基本的玩法就是配置 根据你的业务需要进行配置即可 不需要全部背熟 常用的还有如下这几种
- Before
- Between
- Cookie
- Header
- Host
- Method
网关过滤器
分类
- 局部过滤器:应用于单个路由策略上,其功能仅对路由断言为 true 的请求起作用
- 全局过滤器:应用于所有路由策略上,其功能就像前面的配置文件中设置的默认 Filter
PrefixPath 过滤器(局部)
任何请求都会进行拦截 并在请求地址上增加/info然后将请求转发到目标地址
localhot:9999/findById?id=1 添加之后: localhost:8088/info/findById?id=1
有啥用?保护我们的接口地址 防止被人恶意攻击
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: http://localhost:8088
predicates:
- Path=/**
filters:
- PrefixPath=/info
去除前缀过滤器(局部)
举一反三
localhost:9999/user/findById?id=1 去除之后 localhot:9999/findById?id=1
自定义过滤器(重要)
当内置的过滤器不满足业务需要时 当然也支持自定义的过滤器
比如支付有支付的独立网关,我们可以要求,所有的服务进入支付的相关服务之前需要有支付系统颁发的权限token,此时就可以在支付网关做这样一个校验支付token权限的事情。
需求:往请求头添加一个参数
1.编写自定义filter
public class CustomerFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.构建改变后的request对象
ServerHttpRequest request = exchange.getRequest().mutate().header("X-Request-customer", "customer").build();
//2.将改变的request对象置入exchange对象中
ServerWebExchange webExchange = exchange.mutate().request(request).build();
//3.设置修改后的exchange对象
return chain.filter(webExchange);
}
}
2.配置自定义过滤器生效
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(predicateSpec -> predicateSpec
.path("/**")
.filters(gfs-> gfs.filter(new CustomerFilter()))
.uri("http://localhost:8088")
.id("updateRequest"))
.build();
}
3.另外一个服务(转发过去的服务)http://localhost:8088
@RestController()
@RequestMapping("/info")
public class InfoController {
@GetMapping("/hello")
public String hello(HttpServletRequest request){
String header = request.getHeader("X-Request-customer");
return "new header:" + header;
}
}
4.测试
自定义全局过滤器
模拟鉴权 重要!
全局过滤器 针对所有的路由都生效 只需要交由spring容器管理即可
需求:判断当前请求是否携带了token 如果没有就返回错误信息 (实际开发应校验该token是否合法 也就是这个token存不存在 是否过期以及包含的用户是否合法)
1.定义全局过滤器
@Component
public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求中的token
String token = exchange.getRequest().getQueryParams().getFirst("token");
//校验是否存在
if (StringUtils.isEmpty(token)){
//如果不存在 返回对应的错误码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//如果存在则放行请求
return chain.filter(exchange);
}
@Override
public int getOrder() {
//最高级别 返回的int值越大 优先级越高 spring-core的组件
return Ordered.HIGHEST_PRECEDENCE;
}
}
2.定义一个路由规则 因为是全局的 不需要指定参数filter
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(predicateSpec -> predicateSpec
.path("/**")
.uri("http://localhost:8088")
.id("global_filter_route"))
.build();
}
3.测试
测试1不带 token访问:http://localhost:9004/info/hello
测试2:带token访问http://localhost:9004/info/hello?token=222
过滤器优先级
值越大优先级越高的原则
网关降级
当我们路由到的服务不可用时 一样会报错 此时我们期望 网关可以实现降级 为什么需要降级? 给用户友好提示 不要一堆报错信息 看不懂!
1.添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
2.定义降级处理器controller
@RestController
public class FallbackController {
@RequestMapping("fallback")
public Map<String,String> fallback(){
Map<String,String> map = new HashMap<>();
map.put("code","200");
map.put("msg","服务降级");
map.put("data",null);
return map;
}
}
3.编写降级配置(filter)-针对一个路由
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(predicateSpec -> predicateSpec
.path("/**")
.filters(fs->fs.circuitBreaker(config -> {
//配置降级
config.setName("myCircuitBreaker");
config.setFallbackUri("forward:/fallback");
}))
.uri("http://localhost:8088")
.id("global_filter_route"))
.build();
}
4.测试针对一个路由降级 把提供者服务下线http://localhost:9004/info/hello?token=222
5.配置全局路由熔断 配置文件
spring:
cloud:
gateway:
default-filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
routes:
- id: test_filter
uri: http://localhost:8088
predicates:
- Path=/**
网关限流
针对一些恶意攻击 为了保护网关后面的服务 就可以将一些无效的请求拦截掉 这样的做法也是非常必要的
使用redis 令牌桶
令牌桶:
关键:1.桶(放redis 放令牌 放一定数量的令牌) 2.速率
含义:如果需要执行请求 需要去桶里拿到令牌 才能放行 否则被拒绝并返回
网关令牌桶限流(使用redis实现)
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2.配置文件
spring:
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: requestRateLimiter_filter
uri: http://localhost:8088
predicates:
- Path=/info/**
filters:
- name: RequestRateLimiter
args:
# key-resolver:用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#
key-resolver: "#{@keyResolver}"
# replenishRate:令牌桶每秒填充平均速率。
redis-rate-limiter.replenishRate: 2
# burstCapacity:令牌桶总容量。
redis-rate-limiter.burstCapacity: 5
3.配置类
Bean
//配置令牌同算法key:将主机名作为限流key
public KeyResolver keyResolver(){
return exchange -> Mono.just(exchange
.getRequest()
.getRemoteAddress()
.getHostName());
}
4.测试