Spring Cloud学习(一)-GateWay网关
一、网关基本概念
1.1、API网关介绍
API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
客户端会多次请求不同的微服务,增加了客户端的复杂性。
存在跨域请求,在一定场景下处理相对复杂。
认证复杂,每个服务都需要独立认证。
难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性
假设一个电商项目,购买一件商品需要调用多个微服务接口,才能完成下单操作,在没有使用网关之前,用户的操作流程图是这样的:
在引入网关后,架构可以演变为下图
微服务网关是程序的大门,封装了应用程序的内部结构,用户只需要跟网关交互,无需调用特定微服务接口。
这样,开发就可以得到简化。不仅如此,使用微服务网关还有以下优点:
- 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数。
1.2、Spring Cloud GateWay
1、概述
Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。
Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目的是为了替代Zuul,在Spring Cloud2.0以上版本中,没有对新版本的Zuul 2.0以上的新高版本进行集成,仍然还是使用Zuul 1.x非Reactor模式的老版本。Gateway是基于webFlux框架实现的,WebFlux框架底层使用了高性能的Reactor模式通信框架Netty。
作用:
- 反向代理
- 统一鉴权
- 流量控制
- 熔断
- 日志监控
所处位置
2、核心概念
路由:路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配
- id:路由id,没有起名规则,但要求唯一
- uri:匹配路由的转发地址
- predicates:配置该路由的断言,若符合该断言则会转发到匹配的地址
- order:路由的优先级,数字越小优先级越高
断言:Java8中的断言函数。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。
过滤器:一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。
3、执行流程
如下图所示,Spring cloud Gateway发出请求。然后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。
- Filter中,”pre”类型的过滤器可以做参数校验、权限校验、流量控制、日志输出和协议转换等。”post”类型的过滤器可以做相应内容、响应头的修改、日志的输出、流量监控。
- 核心逻辑:路由转发+执行过滤器链。
4、特点
优点:
- 性能强劲:是第一代网关Zuul的1.6倍
- 功能强大:内置了很多实用的功能,例如转发、监控、限流等
- 设计优雅,容易扩展
缺点:
- 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
- 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
- 需要Spring Boot 2.0及以上的版本,才支持
二、网关使用
2.1、创建网关微服务cloud-gateway-9527
pom.xml
1 | <dependencies> |
application.yml配置文件
application.yml文件中添加路由配置
- -:表示数组元素,可以配置多个节点
- id:配置的唯一标识,可以和微服务同名,也可以起别的名字,区别于其他 Route。
- uri:路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
- predicates:断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
- Path:路径形式的断言。当匹配这个路径时,断言条件成立
- /**:一个或多个层次的路径
1 | server: |
主启动类
1 |
|
2.2、Payment8001
在payment微服务中有两个重要接口
1 |
|
我们希望通过网关在payment服务的8001端口外再包裹一层9527端口,让用户无法直接访问8001端口。
2.3、启动微服务,进行测试
启动eureka7001、payment8001和gateway9527
通过localhost:8001/payment/lb访问payment微服务
通过网关,即9527端口访问payment微服务
访问说明
- payment微服务控制器的访问路径
- 如果访问路径匹配路由断言,则转发到指定uri
2.4、配置路由的另一种方式
使用网关,让网关帮我们跳转到百度新闻页面
- 如果访问路径匹配路由断言/guoji,则帮我们跳转到http://news.baidu.com/guoji
- 如果访问路径匹配路由断言/guonei,则帮我们跳转到http://news.baidu.com/guonei
1、创建一个GateWayConfig配置类
1 |
|
- 使用routes对象的route方法来配置路由断言及要转发的uri
- 其中route接收两个参数,一个为路由id,另一个为jdk8新添加的Function接口对象
- 如果访问路径中有/guonei,那么网关会帮我们跳转到http://news.baidu.com/guonei;如果访问路径中有/guoji,那么网关会帮我们跳转到http://news.baidu.com/guoji
2、测试
2.5、配合注册中心实现动态路由
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
1、启动
根据payment8001微服务复刻出另一个类似的payment微服务,端口号为8002
启动eureka7001+payment8001+payment8002
2、修改网关微服务配置文件
在路由的uri中使用lb://微服务名称的方法来动态配置路由,并实现负载均衡,使用步骤如下
- 在配置文件中开启从注册中心动态创建路由的功能,使用微服务名进行路由
1 | spring: |
- 在配置文件中将路由uri修改为lb://微服务名来指定跳转地址,并实现负载均衡,需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
1 | routes: |
- 网关微服务完整配置文件如下
1 | server: |
3、测试
启动网关微服务,查看eureka注册中心情况
测试,访问payment微服务的lb方法,查看调用端口情况
- 第一次
- 第二次
这样就实现了动态路由和负载均衡功能。
2.6、Predicate的使用
Spring Cloud Gateway内置断言工厂,用于进行条件判断,只有断言都返回真,才会真正执行路由。
内置的断言工厂具体如下
1、基于DateTime
此类型的断言根据时间做判断,主要有三个:
- AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
- BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
- BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
这里的时间格式可以由JDK8自带的ZoneDateTime生成
1 | public static void main(String[] args) { |
- 结果
AfterRoutePredicateFactory
- 在yml配置文件中添加配置如下,这里将after后面的时间设置为我当前时间的一个笑是后
1 | - id: payment_routh2 |
- 结果
- 修改yml配置文件,将时间改为当前时间一个小时前,重启微服务
1 | - After=2021-02-11T14:37:54.926+08:00[Asia/Shanghai] |
- 结果
同理,Before断言和Between断言与After的使用方式类型,只是Between断言需要传入两个时间,如
1 | - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai] |
2、基于远程地址
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
1 | - RemoteAddr=192.168.1.1/24 |
修改yml配置文件,进行测试
1 | - id: payment_routh2 |
- 结果:由于本机地址不在配置的地址内,所以报错
3、基于Cookie
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
1 | - Cookie=chocolate, ch. |
修改配置文件,设置访问时需要带上的Cookie
1 | - id: payment_routh2 |
打开命令行,使用curl进行访问
- 不带Cookie时:curl http://localhost:9527/payment/lb
- 带上Cookie时:curl http://localhost:9527/payment/lb –cookie “wuhu=qifei”
4、基于请求头Header
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。
- 两个参数,一个属性名称和一个正则表达式,当属性值与正则表达式相匹配时执行
1 | - Header=X-Request-Id, \d+ |
修改yml文件
1 | - id: payment_routh2 |
- 使用curl进行测试
1 | curl http://localhost:9527/payment/lb -H "X-Request-Id:123" |
- 结果
- 使用curl进行错误示范
1 | curl http://localhost:9527/payment/lb -H "X-Request-Id:-123" |
- 结果
5、基于Method请求方法
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
1 | - Method=GET |
6、基于Path请求路径
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
最常见的一种断言
1 | - Path=/foo/** |
7、基于Query请求参数
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
1 | - Query=baz, ba. |
修改配置文件
- Query=uname, wuhu断言要求请求中必须有uname参数,且参数值必须为wuhu
1 | - id: payment_routh2 |
三、过滤器
3.1、过滤器基本概念
1、作用
过滤器就是在请求的传递过程中,对请求和响应做一些修改
2、生命周期
客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。
pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现参数校验、权限校验、流量监控、日志输出、协议转换等;
post:这种过滤器在路由到达微服务以后执行。这种过滤器可用做响应内容、响应头的修改,日志的输出,流量监控等。
3、分类
局部过滤器 GatewayFilter:作用在某一个路由上
全局过滤器 GlobalFilter:作用全部路由上
3.2、局部过滤器
1、内置局部过滤器
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload TooLarge | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
2、内置局部过滤器的使用
1 | routes: |
3.3、全局过滤器
1、内置全局过滤器
内置全局过滤器的使用举例:负载均衡过滤器
1 | lb://service-edu |
3.4、自定义全局过滤器
定义一个Filter实现 GlobalFilter 和 Ordered接口
1、作用
- 全局日志记录
- 统一网关鉴权
- …
2、自定义全局过滤器
编写一个过滤器类,这个过滤器用于记录日志已经鉴权
- 如果请求中uname参数值为空,则不放行,直接响应406
- 这个类需要实现GlobalFilter和Ordered接口,并重写两个方法
- 在getOrder方法中,定义过滤器的优先级,order越低,优先级越高
1 | public interface Ordered { |
- 在filter方法中进行日志记录和鉴权操作
1 |
|
- 测试
四、在线教育项目整合GateWay
注意:gateway底层使用的是webflux会与web冲突,所以在gateway中不要引入spring-boot-starter-web依赖
4.1、统一网关鉴权
我们需要在微服务网关中自定义一个全局过滤器,统一处理需要鉴权的服务
1、鉴权逻辑描述
- 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
- 认证通过,将用户信息进行加密形成token,返回给客户端
- 作为登录凭证以后每次请求,客户端都携带认证的token
- 服务端对token进行解密,判断是否有效
对于验证用户是否已经登录鉴权的过程可以在网关统一检验。检验的标准就是请求中是否携带token凭证以及token的正确性。
下面的我们自定义一个GlobalFilter,去校验所有的请求参数中是否包含“token”,如果不包含请求参数“token”则不转发路由,否则执行正常的逻辑。
2、创建自定义过滤器
- 如果token为空或者token不合法,则不放行。
- 这个过滤器的order为0
1 |
|
3、修改前端
guli-site的utils/request.js中修改响应过滤器 ,添加分支:
- 如果响应码为20004,则证明认证失败,用户没有登录,此时让用户跳转到登录页
1 | else if (res.code === 28004) { // 鉴权失败 |
- 修改pages/login.vue的submitLogin方法:登录后回到原来的页面
1 | // 跳转到首页 |
4.2、统一处理跨域问题
1、什么是跨域?
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
2、Spring Boot中解决跨域的几种方法
在gateway中创建一个配置类,这个配置类用于解决跨域问题
1 |
|
在Spring Boot控制器中添加@CrossOrigin注解,这个注解可以加在类上或者方法上
1 |
|
说明,这两种方法只取一种使用即可,如果使用了配置类,那么最好将控制器上的注解删去。
参考教程如下: