1 微服务网关
1.1 为什么需要网关
如果想在微服务架构中实现 权限校验功能,怎么实现?
1. 每个服务自己实现一遍
2. 写到一个公共的服务中,然后其他所有服务都依赖这个服务
3. 写到服务网关的前置过滤器中,所有请求过来进行权限校验
第一种,缺点太明显,基本不用;
第二种,相较于第一点好很多,代码开发不会冗余,但是有两个缺点:
- 由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
- 由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。
第三种,服务网关恰好可以解决这样的问题: - 将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
- 如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。
所以,需要服务网关!!!
1.2 什么是网关
微服务中的网关相当于我们整个微服务的统一入口。
举例:你去了医院看病,那一定会先去分诊台咨询。分诊台护士根据你的需求,如:挂号、取药、复诊、体检等不同需求,为你指路,告诉你应该去哪个地方,怎么走。这个分诊台就相当于医院的网关入口,将流量接入进来,转发到它应该去的地方。
微服务中的网关是介于客户端(外部调用方比如app,h5)和微服务的中间层,是微服务系统的“入口”。
1.3 网关功能(面试)
路由:将外部请求,转发到具体的微服务实例上,是外部访问微服务系统的统一入口。
过滤:过滤功能,负责对请求的处理过程进行干预,实现功能的增强,如:统一鉴权、请求校验、日志监控、流量控制、熔断等功能。
网关本身也是一个微服务,网关可以和注册中心进行整合,将网关服务自身注册为服务,同时从注册中心中获得其他微服务的消息,以后服务路由,都可以通过注册中心获取服务实例信息,实现路由转发功能。
1.4 常见网关(面试)
Zuul:Netflix 公司产品,公司内部产生分歧,有的人想自己出一个Zuul2,不推荐。
*Zuul2:也是Netflix 公司准备出的产品,但是由于内部分歧,所以Zuul2已经胎死腹中了,不推荐。*
gateway:Spring社区自己出的网关组件,官方隆重介绍和极度推荐的网关服务组件,推荐。
官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter
1.5 Gateway 入门
1.5.1 相关概念
Route(路由):网关的基本构建块。它由服务ID、目标URI,谓词集合和过滤器集合定义。如果聚合条件为true,则匹配路由。
Predicate(谓词、断言(条件判断)):用于匹配HTTP请求中的所有内容,例如标头或参数,理解为匹配HTTP请求的条件,请求符合断言的条件,就向后路由。
Filter(过滤器):这些是GatewayFilter由特定工厂构造的实例。在这里,您可以在发送下游请求之前或之后修改请求和响应,实现过滤器功能。
1.5.2 工作原理(重要)
1.5.3 Gateway入门案例(路由)
需求:前台访问网关,网关路由到某个服务
- 创建SpringBoot工程gateway工程,引入网关依赖
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 编写启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
- 编写基础配置和路由规则
server:
port: 5001 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
uri: http://localhost:8001 # 路由的目标地址 http就是固定地址 目标服务地址
# uri: lb://consumer # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/consumer* # 这个是按照路径匹配,只要以/consumer开头就符合要求
- 启动网关服务进行测试
1.5.4 Gateway配置详解
官方解释:
查看源代码,解析gateway与配置类的映射关系:
1.5.5 基于服务名称路由案例
一个服务往往会发布多个实例,如果路由的URI写死,固定向一台服务器路由也不合适。
Gateway默认支持了基于Ribbon的负载均衡,默认规则是轮询,可通过服务名直接路由:
以前的:
现在的:
1.5.6 断言匹配(高级应用-了解)
Predicate(断言)定义了URI与服务的匹配条件,符合Predicate的请求,才能转到后面的服务中。
断言有很多种:
Predicate(断言) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:
基于Datetime
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
基于Cookie
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
Cookie=chocolate, ch.
基于Header
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。
Header=X-Request-Id, \d+
基于Path请求路径
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
Path=/foo/**
基于Query请求参数
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
localhost:8080/aaa?id=1&name=zs
Query=id, \d+
基于路由权重
WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发
routes:
- id: weight_route1
uri: host1
predicates:
- Path=/product/**
- Weight=group3, 2
- id: weight_route2
uri: host2
predicates:
- Path=/product/**
- Weight= group3, 8
断言匹配规则
在predicates配置下,以数组的形式配置多个断言选项。
格式为:每一个断言类型后跟一个等号(=),然后是由逗号分隔的参数值(,)。
1.6 Gateway过滤器
1.6.1 过滤器概述
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
gateWay 提供了路由过滤器功能,可以实现在请求到达前、请求响应前做出过滤操作。
SpringCloud gateway官方提供了许多内置的过滤器,通过简单的yml配置即可实现,当然也可以自定义过滤器,在自定义过滤器中,实现如:日志记录、统一鉴权、限流、熔断 等操作。
Spring提供了31种不同的路由过滤器工厂:
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
常用的也就那么几个,例如:
名称 | 说明 |
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
网关过滤器的继承体系:
1.6.2 过滤器配置入门案例(了解)
客户端请求到达网关,网关在请求中使用【AddRequestHeader】过滤器给当前请求添加请求头,然后再转发到后面的微服务中,网关处配置文件如下:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
在微服务中,就可以从请求头中获取到【X-Request-red】请求头的值为【blue】
过滤器可以根据它的作用范围,分为全局过滤器和局部过滤器,上面的案例是局部过滤器,仅针对它所在路由规则生效。
如下配置,是全局过滤器配置,针对所有路由规则生效:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
default-filters:
- AddRequestHeader=X-Request-red, blue
1.6.3 其他过滤器配置使用(了解)
StripPrefix去掉前缀
有的时候,希望在路由时,在URL中添加一层路径,用于区别不同的服务,或者其他用途。
然后在网关向后路由时,使用过滤器 StripPrefix属性,将这添加的这一层路径自动去掉,如下图:
去掉几层前缀,StripPrefix就写几。
PrefixPath添加前缀
添加前缀与去掉前缀刚好相反,去掉前缀是请求在网关处,向后路由时,去掉一层前缀,而添加前缀是向后路由时,自动添加指定的前缀,如:
1.6.4 过滤器执行顺序(了解)
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序的规则是什么呢?
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
1.6.5 过滤器实现-登录校验
应用自定义全局过滤器:
在全局过滤器中,可以实现统一认证、授权、日志记录、熔断、限流等需要在网关处统一进行的各种行为。
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 请求参数是否包含token,
- token请求参数的值是否为jiyun
如果同时满足则放行,否则拦截,返回无访问权限信息。
1.创建过滤器
在服务网关中定义过滤器只需要实现 GlobalFilter, Ordered接口就可对请求进行拦截与过滤。
@Component
public class LoginFilter implements GlobalFilter, Ordered {
/**
* 过滤器的业务逻辑
* @param exchange:请求和响应的上下文,存储了Request和Response对象
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (!"jiyun".equals(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();//结束请求
}
return chain.filter(exchange);//放行
}
/**
* 过滤器的执行顺序:通过整数表示顺序,数值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
2.测试
不加token测试
加token测试
1.7 网关处统一处理跨域(面试)
跨域资源共享(Cross-Origin Resource Sharing,CORS)是指在 Web 应用中,浏览器发现访问的目标资源违背了同源策略,所作出的限制行为。
同源策略,即:访问的域名、端口、协议有任意一项与当前页面的域名、端口、协议不一致,就会引起跨域问题,这是发生在浏览器端,浏览器会进行控制。
举例来说,如果一个页面在域名为 example.com 的服务器上运行,但是需要获取域名为 api.example.org 的服务器上的数据,就会遇到跨域问题。因为不同的域名被认为是不同的安全域,浏览器会禁止页面访问不同源的资源。
浏览器会在发生同源策略的时候,先向目标资源发起一次预检请求,如果后端应答了本次请求,则浏览器会发起第二次正确请求。所以处理跨域问题,需要后端的支持。
为了解决跨域问题,需要在服务器端进行一些配置,允许不同源之间的通信。通常的解决方案是通过设置 HTTP 头部信息中的 Access-Control-Allow-Origin 字段,来允许指定的源可以访问服务器上的资源。
在微服务项目中,我们在网关微服务处,直接针对跨域问题进行统一处理即可,通过编写全局过滤器实现。
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
案例演示:
1、编写一个前端项目,前端项目向微服务网关发起ajax请求,此时端口号不一样,一定会被跨域拒绝;
2、在微服务网关处,编写跨域处理配置,再次测试,发现跨域问题被处理掉了。
1.8 灰度发布(了解)
当服务功能更新迭代后,该如何发布?
A、立刻使用新版本替换所有旧版本服务,万一新版本有bug,意味着新版本集体下线,导致整体服务暂时不可用。
B、逐步替换,同时部署新版和旧版服务,请求到达后,释放少量请求到新版服务中,经过一定时间测试,服务稳定后,再逐步替换其他旧版服务,如果中途出现不稳定情况,可以随时下线新版服务及时维护,线上由旧版服务继续服务。这里就需要用到灰度发布实现了。
灰度发布是当服务版本更新时,通过开放适量请求到新版本服务,当经过测试,新版本服务功能稳定后,逐步更新服务版本的版本发布方式。
如何实现灰度发布:
针对相同的访问路径,配置不同的路由URI,路由到不同的服务地址:
利用断言提供的权重配置,实现随机路由效果,达到灰度发布。
routes:
-
id: weight_route1
uri: lb://bg-consumer
predicates:
- Path=/consumer*
- Weight=group1, 70
-
id: weight_route2
uri: lb://bg-consumer-v2
predicates:
- Path=/consumer*
- Weight= group1, 30
要实现动态修改网关微服务,而不重启网关微服务,可以将配置文件放到nacos配置中心,动态修改配置中心的配置即可。