微服务网关概述
在学习完前面的知识后,微服务架构已经初具雏形。但还有一些问题:
- 不同的微服务一般会有不同的网络地址
- 客户端在访问这些微服务时必须记住几十甚至几百个地址
- 这对于客户端方来说太复杂也难以维护。
- 微服务项目开发, 每一个功能都对应一个模块~
而每个模块又都是一个独立的小型项目工程:具有独立的 ip 端口...
- 这种情况就会导致:
1.在某些场景下存在跨域请求的问题
2.加大身份认证的难度,每个微服务需要独立认证
3.客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
因此,我们需要一个微服务网关
- 介于客户端与服务器之间的中间层:
所有的外部请求都会先经过微服务网关。
- 客户端只需要与网关交互,只知道一个网关地址即可
这样简化了开发还有以下优点:
- 易于监控
- 易于认证
- 减少了客户端与各个微服务之间的交互次数
什么是微服务网关
- API网关是一个
服务器
, 是系统对外的唯一入口
客户端不需要在记录 大量的微服接口只需要记住一个 网关服务就行了, 通过它就可以找到需要的服务接口; - API网关方式的核心要点是: 所有的 客户端 和 消费端 都通过统一的网关接入微服务.
在网关层处理所有的非业务功能。
- 通常,网关也是提供
REST/HTTP的访问API
。服务端通过API-GW注册和管理服务。
作用和应用场景
网关具有的职责如:
身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。
常见的API网关实现方式
Kong
- 基于
Nginx
+Lua
开发 `稍后整理Nginx笔记!` 优点:
性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。问题:
只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
Zuul 👍
- Netflflix 公司开源
- 功能丰富,使用
JAVA开发
,易于二次开发;需要运行在web容器中
如Tomcat。
- 问题:
缺乏管控,无法动态配置;依赖组件较多
处理Http请求依赖的是Web容器,性能不如 Nginx
;
Traefifik
- Go语言开发
- 轻量易用;提供大多数的功能:服务路由,负载均衡等等;提供WebUI
- 问题:
二进制文件部署,二次开发难度大;UI更多的是监控,缺乏配置、管理能力
Gateway 👍
- SpringCloud提供的网关服务!!
Zuul简介
- ZUUL是
Netflflix开源
的微服务网关它可以和Eureka、Ribbon、Hystrix等组件配合使用
(都是一家的怎么见外😥?)
Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
- 动态路由:
动态将请求路由到不同后端集群
- 压力测试:
逐渐增加指向集群的流量,以了解性能
- 负载分配:
为每一种负载类型分配对应容量,并弃用超出限定值的请求
对于不符要求请求直接丢弃 - 静态响应处理:
为每一种负载类型分配对应容量,并弃用超出限定值的请求
页面的响应在 网关进行展示不会影响到 服务模块~
- 身份认证和安全:
识别每一个资源的验证要求,并拒绝那些不符的请求。
添加一个模块 zuul_server 网关模块
继续 三板斧: 依赖-注解-配置
导入需要依赖:
pom.xml
<dependencies>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- starter-netflix-zuul:所有zuul的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!-- 网关的限流配置依赖: -->
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.3.4.RELEASE</version>
</dependency>
</dependencies>
- zuul 网卡, 属于服务
- 总的来说也还是一个
SpringBoot 微服务项目
- 而且 它要对 服务模块接口 进行获取管理, 就要去注册中心中获取管理的模块接口;
- 所有它也是一个 Eureka-client
编写启动类Myzuul
Myzuul.Java
@SpringBootApplication
@EnableZuulProxy //通过 @EnableZuulProxy 注解开启Zuul网管功能
public class Myzuul {
public static void main(String[] args) {
SpringApplication.run(Myzuul.class, args);
}
}
- @EnableZuulProxy
注解开启Zuul网管功能
- 因为它是依赖注册中心的:
底层应该已经开启了支持所有注册中心的客户端注解了…@EnableDiscoveryClient个人猜测, 因为并不需要声明 Eureka的客户端注解:
@EnableEurekaClient
创建配置文件 application.yml
并添加相应配置
application.yml
server:
port: 7004 #zuul网关端口
spring:
application:
name: zuul-server #zuul网关微服名~
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ #指定注册的注册中心~
instance:
prefer-ip-address: true #显示浏览器中的状态栏显示ip
#zuul网关配置
zuul:
routes:
#定义路由,因为是自定义的所有下面并没有提示~
myuser-consumer: #这里是路由id, myuser-consumer是随意写,后面根据 路由做:限流操作!
path: /userserver/** #这里是映射路径,指在网关请求时候,需要在原请求下加上前缀进行访问!
serviceId: user-server #指定当前路由的——微服务名(到注册中心中找对应的名获取对应的 ip 端口~)
- path:
这里是映射路径, 指在网关请求时候,需要在原请求下加上前缀 userserver
进行访问!
当然也可以不设置前缀:直接 /**
- 设置前缀是为了软件开发中,区分当前是那给模块下的
接口
- zuul 这里设置的前缀并不会对调用方模块没有任何影响, 只是网关调用需要 +前缀!
测试:
ok,这样就大致完成了!
可以看到, 通过网关完成了 服务的调用! 这样只需要给前端网关的 ip 端口即可~
ZUUL 搭建总结:
- 说实话实操一遍下来, 并不是很难, 关键是理论…有点像负载均衡~
负责管理分配:
- 负责均衡是:
通过算法机制 对多个提供者的均衡调用...减轻服务器压力..
- 网关:
是统一多个微服务 IP 端口 方便前端的访问调用...减轻前端的压力
- 实现的话
三板斧原则: 依赖-注解-配置
导入依赖------主程序启动网关注解:@EnableZuulProxy
-------.yml的网关配置:
Zuul 管理的架构
- pc / 移动 通过网关进行请求:
- Zuul网关去注册中心去拉取 服务 并做出 对应的
请求服务
(当然, 事先这些服务都注册到注册中心去...)
Zuul中的过滤器
通过之前的学习,我们得知Zuul它包含了两个核心功能:对请求的 路由 和 过滤
动态路由:负责将外部请求转发到具体的微服务实例上
是实现外部访问统一入口的基础;
过滤器:
负责对请求的处理过程进行干预: 是实现请求校验、服务聚合等功能的基础。
例如:验证用户是否登录校验 未登录直接过滤!
路由功能在真正运行时
它的路由映射和请求转发同样也由几个不同的过滤器完成的。
所以,过滤器可以说是Zuul实现API网关功能最为核心的部件
,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
ZuulFilter 拦截简介:
Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。 (类似于AOP的环绕增强!)
- PRE
这种过滤器在请求被路由之前调用。
我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 - ROUTING
这种过滤器将请求路由到微服务。用于构建发送给微服务的请求(就是在PRE过滤器之后执行)
使用Apache HttpClient或Netfifilx Ribbon请求微服务。
- POST
这种过滤器在路由到微服务以后执行。
这种过滤器可用来为响应添加标准的HTTP
Header、收集统计信息和指标、将响应从微服务发送给客户端等。 - ERROR
在其他阶段发生错误时执行该过滤器。
- 不同过滤器的场景:
请求鉴权: 一般放在pre类型,如果发现没有访问权限,直接就拦截了
异常处理: 一般会在error类型和post类型过滤器中结合来处理。
服务调用时长统计: pre和post结合使用。分布获取当前时间 相差即可!
执行流程:
正常流程:
- 请求到达首先会经过pre类型过滤器
- 而后到达routing类型,进行路由请求就到达 真正的服务提供者;
- 执行请求,返回结果后,会到达post过滤器。而后返回响应。
异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器
再error 处理完毕后,会将请求交给POST过滤器,最后返回给用户。 - 如果是error过滤器自己出现异常,
最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,
会跳转到error过滤器,但是与pre和routing不同的时, 请求不会再到达POST过滤器了。
验证当前是否Token登录!
创建过滤器包 fitter
实现用户登录: 是否存在Token 如果没有则过滤掉请求!
PowerFilter.Java
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
//使当前类被Spring容器管理!
@Component
public class PowerFilter extends ZuulFilter { //继承ZuulFilter抽象类,并实现拦截器方法;
@Override
public String filterType() { //当前拦截器类型;
return FilterConstants.PRE_TYPE; //根据常量来确定当前过滤器类型 pre 路由前执行~
} // 常量值就是pre直接写字符pre也可以
@Override
public int filterOrder() {
return 0; //设置过滤器级别 值越大执行顺序越慢
}
@Override
public boolean shouldFilter() {
return true; //有可能存在多个拦截器,设置false 则忽略之后的过滤器!直接执行代码!
}
@Override
public Object run() throws ZuulException { //主要过滤的操作run()
System.out.println("zuul 前过滤!");
//Java原生的Servlet类: RequestContext 获得请求上下文对象
RequestContext currentContext = RequestContext.getCurrentContext();
//获取去一个request 对象,根据对象获取页面参数是否存在 Tonken如果没有Tonken则直接报错不给登录!
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
//判断
if (token == null || token.equals("")) {
//发送zuul 响应终止. 过滤器~
currentContext.setSendZuulResponse(false);
//给浏览器发送当前状态码!
currentContext.setResponseStatusCode(401);
//给浏览器返回响应数据!
currentContext.setResponseBody("{'code:':401,'data':'token not found!'}");
}
return "next"; //通过当前 过滤器
}
}
测试
ZUUL 过滤总结:
- 实现
ZuulFilter
抽象类重写方法!根据需求编写对应的方法:
- 在 run() 方法中编写过滤的操作…
通俗易懂: 为了防止请求流量过大, 程序服务器扛不住压力! 对请求流量做出限制的操作!
常见的限流算法
计数器 可以直接通过配置来设置的 ip 限流
- 计数器限流算法是最简单的一种限流实现方式。
限制一定时间内访问的数量...
- ZUUL可以直接通过配置 .yml 来设置的 ip 技术限流
.yml
#zuul网关配置
zuul:
routes:
#定义路由,因为是自定义的所有下面并没有提示~
myuser-consumer: #这里是路由id, myuser-consumer是随意写,后面根据 路由做:限流操作!
path: /userserver/** #这里是映射路径
serviceId: user-server #指定当前路由的——微服务名(到注册中心中找对应的名获取对应的 ip 端口~)
ratelimit:
enabled: true #开启限流
policies:
#指定 路由 进行的限流设置!
myuser-consumer:
limit: 10 #60s 内请求超过10次,服务端就抛出异常,60s后可以恢复正常请求,抛出异常就会被全局的异常处理接受导!
refresh-interval: 60
type: origin #针对IP进行限流,不影响其他IP
#还可以通过如下配置来开启全局的服务限流! default-policy:
#但不建议! 正常情况下只是对某一个高访问的模块进行限流..没必要所有模块都做限流!
# ratelimit:
# enabled: true #开启限流
# default-policy: #所有的路由都限流...
# limit: 3
# refresh-interval: 60
# type: origin
测试:
Spring全局异常处理!
MyErrorController.Java
@RestController
public class MyErrorController implements ErrorController {
@Override
public String getErrorPath() {
return "error";
}
@RequestMapping("/error")
public String error(HttpServletResponse response)
{
int code = response.getStatus();
if (404 == code) {
System.out.println(404+"未找到资源");
} else if (403 == code) {
System.out.println(403+"没有访问权限");
} else if (401 == code) {
System.out.println(401+"登录过期");
} else {
System.out.println(500+"服务器错误");
}
//可以根据返回的 状态码来返回指定的页面!
return "{\"result\":\"访问太多频繁,请稍后再访问!!!\"}";
}
}
- SpringBoot 类实现ErrorController 接口来完成
全局异常处理
如果避免出现404,405,500这种报错信息时, 可以通过
全局异常处理 - 实现接口 重写getErrorPath() 方法…指定异常跳转的URL
总结:
- 技术限流 ZUUL可以直接通过 yml配置直接修改!
实现对应用的限流
- 超出的请求, 会抛出异常! 可以通过 SpringBoot全局异常来捕获处理!
漏桶算法
- 漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。
- 漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。
- 在网络中,漏桶算法可以控制端口的流量输出速率,
平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。 - 为了更好的控制流量,漏桶算法需要通过两个变量进行控制:
一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。 - 总的来说就是:
水龙头比作请求 不管你多大的请求我都, 先经过的的 桶;
桶底有一个孔 ,决定了可以通过的请求 平稳的流速 完成请求…
如果有溢出的请求服务, 则就是直接抛弃…
令牌桶算法
在漏桶算法基础上的更改:
- 桶算法能够限制请求调用的速率,
无论请求如何都不会超出某一个请求值
- 而令牌桶算法能够在限制调用 的平均速率的同时还允许一定程度的突发调用。
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌
- 算法中存在一种机制,以一定的速率往桶中放令牌。
每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行 否则选择等待可用的令牌、或者直接拒绝。 - 放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌
- 所以就存在这种情况,桶中一直有大量的可用令牌,
- 这时进来的请求就可以直接拿到令牌执行,比如设置qps并发请求为100,
- 那么限流器初始化完成一秒后,桶中就已经有100个令牌了,该限流器可以抵挡瞬时 的100个请求。
- 所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
(一次创建多少令牌 允许多少请求的匀速请求)
实现令牌桶算法:
首先注释 .yml中的 计数限流
避免冲突~
RouteFilter.Java
import com.google.common.util.concurrent.RateLimiter; //谷歌的组件..
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
//令牌限流:
@Component
public class RouteFilter extends ZuulFilter {
//设置常量令牌每秒产生的次数;
//定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
private static final RateLimiter rateLimiter = RateLimiter.create(2);
//过滤器类型 pre 路由前执行
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
//级别 越小越先执行
@Override
public int filterOrder() {
return -10;
}
//方法返回 ture/false ture继续执行,false跳出不在执行后面~
@Override
public boolean shouldFilter() {
System.out.println("令牌生效");
RequestContext currentContext = RequestContext.getCurrentContext();
//判断当前是否存在令牌:没有令牌则不同行!
if (!rateLimiter.tryAcquire()) { //RateLimiter的方法底层会自动获取当前是否存在令牌...
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(401);
currentContext.setResponseBody("{'code:':401,'data':'limit'}");
return false;
}
//如果成立则同行~
return true;
}
//run().....拦截器的代码...
@Override
public Object run() throws ZuulException {
return null;
}
}
令牌桶对象: RateLimiter
- tryAcquire()
只要能够马上获致到1个令牌,则返回true,不阻塞 - tryAcquire(5, 3, TimeUnit.SECONDS)
在3秒钟内可以获取到5个令牌,则返回true,不阻塞 - acquire(5)
获取到5个令牌,否则一直等待,会阻塞,返回值为阻塞的时长 - acquire()
获取到1个令牌,否则一直等待,会阻塞,返回值为阻塞的时长
测试:
总结:
- 貌似没啥总结的, 代码不难… 需要掌握理论!
GateWay
:Zuul网关存在的问题
Zuul1x版本
本质上就是一个同步Servlet
,采用多线程阻塞模型进行请求转发。
Servelt 会问每一个请求,专门分配一个线程来执行… 直到客户端响应线程才返回 线程池
而 Servelt 是Java编写的… 对于多线程的操作并不完善…线程池大小存在限制.
如果, 后台频繁调用比较耗时的业务
那么 , 执行的线程就会堵塞来完成该功能~ 线程资源会被占用很容易耗尽容器线程池内的线程,造成容器无法接受新的请求。不支持任何长连接
Zuul 2.x
(基于Netty,也是非阻塞的,支持长连接)
但Spring Cloud 暂时还没有整合计划。
Spring Cloud Gateway 比 Zuul 1.x 系列的性能和功能整体要好。
GateWay
Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0
难怪不计划对Zuul 2.x整合
有亲儿子了…SpringCloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflflix ZUUL
其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能 例如:
安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。RPS: 即Requests Per Second的缩写,
每秒能处理的请求数目。
Spring Cloud Gateway 的RPS 是Zuul的1.6倍
GateWay核心概念
路由(route)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由断言(predicates)
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
过滤器(filter)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
依赖
pom.xml
<!-- SpringCloud提供的 gateway依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
注意
- spring-cloud-starter-gateway依赖 和 spring-boot-startet-web 有冲突
建议Maven开发的话: 父工程不要在放 web依赖.. 不然会发生冲突... 移动到需要的子模块配置 web依赖...
主程序
Mygateway.Java
@SpringBootApplication //SpringBoot主程序
@EnableEurekaClient //开启注册中心客户端注解
public class Mygateway {
public static void main(String[] args) {
SpringApplication.run(Mygateway.class, args);
}
}
.yml 配置
application.yml
server:
port: 7005
spring:
application:
name: gateway-server
#gateway 的配置...
cloud:
gateway:
routes:
- id: user-server #自定义的路由 ID,保持唯一
uri: http://127.0.0.1:6002 #通过uri 指定目标服务地址,可以通过 lb://指定注册中心的服务,实现动态调用。。。请求
predicates: #路由规则,返回一个布尔值结果。符合条件才能请求~
- Path=/user/** #请求前缀需要 /user 才能请求! (调用的服务也要有 /user前缀!)
#Eureka 注册服务;
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
prefer-ip-address: true #显示浏览器中的状态栏显示ip
测试:
网关 和 微服的请求前缀必须加 /user
才能进行请求!
微服 controller层加上:@RequestMapping("/user")
常用配置:
动态路由 url
上面的url 写四了请求, 当然也可以同注册中心
来完成动态调用
- 首先确保存在
注册中心!
application.yml
lb://指定注册中心的服务,实现动态调用。。。请求
cloud:
gateway:
routes:
- id: user-server
# uri: http://127.0.0.1:6002
uri: lb://user-server #动态请求 user-server的 ip 端口...
predicates:
- Path=/user/**
重写转发路径
- 我们通过网关请求时候, 有时需要在请求前加上前缀,
但并不像要修改 微服务的请求方式, 只是像网关的请求 +前缀; - 可以通过
RewritePath配置重写转发的url
例如:将/user-server/(?.*)重写
为${segment},然后转发到订单微服务。
网关上请求http://localhost:8080/user-service/user/1
此时会将请求转发到http://localhost:8080/user/1
( 值得注意的是在yml文档中 $ 要写成 $\ )
测试
首先把 user-server模块的@RequestMapping("/user")
移除掉~
ok, 发现网关请求 需要前缀, 而服务单独请求不在需要了…! 完成!
路由规则
上面 .yml配置可以查看, 创建一个路由需要:id
url
规则
常见的网关规则:
建议浏览一遍, 需要时候copy即可
#路由断言之后匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://xxxx.com
#路由断言之前匹配
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
#路由断言之前匹配
- id: before_route
uri: https://xxxxxx.com
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
#路由断言之间
- id: between_route
uri: https://xxxx.com
predicates:
- Between=xxxx,xxxx
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
curl -H 'Cookie:name=forezp' localhost:8081
- id: cookie_route
uri: https://xxxx.com
predicates:
- Cookie=name, forezp
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
- id: header_route
uri: https://xxxx.com
predicates:
- Header=X-Request-Id, \d+
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
- id: host_route
uri: https://xxxx.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
#路由断言Method匹配,匹配的是请求的HTTP方法
- id: method_route
uri: https://xxxx.com
predicates:
- Method=GET
#路由断言匹配,{segment}为可变参数
- id: host_route
uri: https://xxxx.com
predicates:
- Path=/foo/{segment},/bar/{segment}
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含 foo,并且foo的值匹配ba.)
- id: query_route
uri: https://xxxx.com
predicates:
- Query=baz 或 Query=foo,ba.
#路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位数即255.255.255.0
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
Gate way 过滤器:
- Spring Cloud Gateway除了具备请求路由功能之外
- 也支持对 请求的过滤。 通过Zuul网关类似,也是通过过滤器的形式来实现的。
Gate way 过滤器的生命周期
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:pre
和 post
。
PRE
这种过滤器在请求被路由之前调用。
- 我们可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
POST
这种过滤器在路由到微服务以后执行。
- 这种过滤器可用来为响应添加标准的:
HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
Gate way 过滤器类型
Spring Cloud Gateway 分为另外两种 GatewayFilter
(内置过滤器) 与 GlobalFilter
(自定义过滤器)。
GatewayFilter 内置过滤器:局部过滤器。
GatewayFilter 又称
局部过滤器
是针对单个路由的过滤器。
声明在单个, 路由id下,只有改路由存在改过滤器!- 在Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的
局部过滤器。
每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾
1. Spring Cloud Gateway的一个约定
RewritePath 对应的实现类就是 RewritePathGatewayFilterFactory idea 查找类
Ctrl+N 输入:RewritePath
可以在类中方一个断点,运行类。发现程序暂停!!ctrl+N 输入GatewayFilterFactory 它是一个接口 内置过滤器集合!
GlobalFilter 自定义过滤器:全局过滤器
全局过滤器(GlobalFilter)和 局部相反就是作用于所有路由
- Spring Cloud Gateway 定义了
GlobalFilter
(全局过滤器)接口 - 使用户可以自定义实现自己的Global Filter。
通过全局过滤器可以实现对权限的统一校验,安全性验证等功能,并且全局过滤器也是程序员使用比较多的过滤器。
Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发
进行处理如下:
Gate way 全局过滤器开发:
- 内置的过滤器已经可以完成大部分的功能
- 但是对于企业开发的一些业务功能处理,
还是需要我们自己编写过滤器来实现的,通过代码的形式自定义一个过滤器,完成统一的权限校验。
功能场景:统一鉴权
开发中的鉴权逻辑:
- 当客户端第一次请求服务时,服务端对用户进行信息认证(是否登录)
- 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
以后每次请求,客户端都携带认证的token 服务端对token进行解密,判断是否有效。
代码实现
- 自定义一个GlobalFilter
- 去校验所有请求的请求参数中是否包含“token”,
- 如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。
TokenFitter.Java
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
//Gate way实现过滤器:
@Component
@Slf4j
public class TokenFitter implements GlobalFilter, Ordered { //自定义过滤器类 实现GlobalFilter Ordered接口并实现两个方法;
//执行过滤
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//根据参数 exchange 获得request对象 并从url中获取参数...
String token = exchange.getRequest().getQueryParams().getFirst("token");
//判断是否存在Token
if (token == null || "".equals(token)) {
log.info("用户没有登录, 权限被拦截!");
//返回页面状态码!
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//终止请求!
return exchange.getResponse().setComplete(); //退出..,拦截拦截退出...
}
//存在Token 则同行;
return chain.filter(exchange);
}
//过滤顺序级别
@Override
public int getOrder() {
return 0;
}
}
总结:
自定义全局过滤器需要实现GlobalFilter和Ordered接口。
在filter方法中完成过滤器的逻辑判断处理
在getOrder方法指定此过滤器的优先级,返回值越大级别越低ServerWebExchange 就相当于当前请求和响应的上下文
存放着重要的请求-响应属性、请求实 例和响应实例等等。
一个请求中的request,response都可以通过 ServerWebExchange 获取调用 chain.filter 继续向下游执行
(放行)
exchange.getResponse().setComplete(); 终止请求;
测试:
这时候,你所有的 网关请求
就必须要带着 Token才可以执行请求。 不然都会被 全局拦截!
这里这是一个展示,更多功能请自己学习… 学会了教我~
Gateway 基于Filter(过滤器) 的限流
SpringCloudGateway官方就提供了基于令牌桶的限流支持。
默认就提供了 令牌桶的限流支持!
基于其内置的过滤器工厂
RequestRateLimiterGatewayFilterFactory 实现
在过滤器工厂中是通过
Redis
和lua
脚本结合的方式进行流量控制。使用Redis 进行监听并对 相同数量进行限制!
环境搭建
启动redis服务
redis 目录下:redis-server.exe
开启Redis 的监听
首先启动 Redis 服务
导入 pom 依赖:
pom.xml
<!--监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
修改 .yml 配置:
spring:
application:
name: gateway-server
#gateway 的配置...
cloud:
gateway:
routes:
- id: user-server #自定义的路由 ID,保持唯一
# uri: http://127.0.0.1:6002 #通过uri 指定目标服务地址,可以通过 lb://指定注册中心的服务,实现动态调用。。。请求
uri: lb://user-server
predicates: #路由条件,返回一个布尔值结果。符合条件才能请求~
- Path=/user/** #请求前缀需要 /user 才能请求! (调用的服务也要有 /user前缀!)
filters:
- RewritePath=/user/(?<segment>.*), /$\{segment} #网关请求的 /user/** 请求会重写为 /** 请求~
#Gateway 限流过滤操作! 默认令牌桶操作!
- name: RequestRateLimiter
args:
key-resolver: '#{@tokenKeyResolver}' # 使用SpEL从容器中获取对象,用户自定义的类!
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 3 # 令牌桶的总容量
#Spring 节点下配置redis依赖!(本人没有密码!)
redis:
host: 127.0.0.1
port: 6379
在 application.yml 中添加了
redis的信息
,并配置了RequestRateLimiter
的限流过滤器- name: RequestRateLimiter
: 设置Gate way的限流配置:
key-resolver 使用SpEL从容器中获取对象 {@beanName} 从Spring 容器中获取 Bean 对象。开发者自定义的类!
burstCapacity 令牌桶总容量。
replenishRate 令牌桶每秒填充平均速率。
开发者根据需求定义 限流类:LimitConfig
- 配置KeyResolver 请求限制的条件!
- 为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。
本次简单实现两种: 根据ip 限流 根据token 登录用户限流
LimitConfig.Java
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
//gatewat 完成限流: 采用令牌桶限流法....
@Configuration
public class LimitConfig {
//根据ip地址进行 限流,
/* @Bean
public KeyResolver pathKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
System.out.println("ip 限流");
String addr = exchange.getRequest().getPath().toString();
System.out.println("===================:" + addr);
return Mono.just(addr);
}
};
}*/
//根据token 登录用户限流...
@Bean
public KeyResolver tokenKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
System.out.println("token 限流");
String addr = exchange.getRequest().getQueryParams().getFirst("token"); //获取请求中的Token 打印;
System.out.println("===================:" + addr);
//返回,Redis会记录每次的返回结果. 对一样的解决进行计数.令牌桶限流!
//同一个Token 一端时间请求多次超过令牌桶则不在进行响应直接限制访问!!等待新的令牌!
return Mono.just(addr);
}
};
}
}
- 类中有两个方法:
tokenKeyResolver
pathKeyResolver
分别是两种不同的限流方式; - 使用时直接修改 yml:
key-resolver: '#{@方法名}'
配置限流的操作!
测试:
Gate way 网关 .yml 配置:Gate way网关统一实现: 跨域请求
.yml
配置:使网关统一完成 跨域的操作!
#Spring 配置下
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
微服模块区分请求 Path ,简化模块请求
网关管理者很多的微服务暴漏的 接口
为了方便区别通常会设置前缀… 而一个微服务可能会有很多的 controller 又作不同的事情(进行区分)…
- 当用户访问/api/user/的时候我们再根据用户请求调用用户微服务的指定方法。
- 当然,除了/api/user/还有/api/address/、/api/areas/、/api/cities/、/api/provinces/都需要由user微服务处理
-
- Path=/api/...
用户模块下的多个功能 Contrller的区分可以, 使用, 逗号
进行分隔区分… - filters: 过滤器
- StripPrefix=1
移除微服模块 /api/user...
请求前缀的前一个… 这里就是移除 /api/
Gateway 的请求前缀是要在 微服模块和网关都要有前缀才可以的请求.通常都会在微服务模块加入 @RequestMapping("/api/user")
而 StripPrefix=1 就可以省去微服务模块 @RequestMapping("/user")
的 /api/ 前缀! 很大程度上简化了开发!优化格式