目录
1.网关
1.1 网关简介
1.2 网关组件
1.2.1 Gateway介绍
1.2.2 Gateway实践
1.2.3 Gateway执行流程
1.2.4 断言工厂
1.2.5 过滤器
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
1.客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
2.认证复杂,每个服务都需要独立认证。
3.存在跨域请求,在一定场景下处理相对复杂。
上面的这些问题可以借助API网关来解决。所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。架构如下图所示:
在业界比较流行的网关,有下面这些:
Ngnix+lua
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用,lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
Kong
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。Zuul 1.0
Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发
问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器。
SpringCloud Gateway
Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的APl路由管理方式。它的目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
1·性能强劲:是第—代网关Zuul的1.6倍。
2·功能强大:内置了很多实用的功能,例如转发、监控、限流等。
3.设计优雅,容易扩展。
缺点:
1.其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高。
2.不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行。
3.需要Spring Boot 2.0及以上的版本,才支持。
这里模拟通过浏览器访问网关,然后网关将请求转发到订单微服务,通过查询订单返回订单信息,在其中过程中订单中有用户信息,需要根据订单中的用户id调用用户微服务进行查询然后进行赋值然后返回订单信息。
新建一个网关微服务
导入依赖
//注意导入了gateway网关不要导入Spring-boot-starter-web依赖,有冲突
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
配置application.yml
server:
port: 7000
spring:
rabbitmq:
username: admin
password: admin
host: 182.92.167.13
port: 5672
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes: #路由数组,指当请求满足什么样的条件转发到哪个微服务上
- id: order_router #当前路由标识,要求唯一
uri: lb://provider #请求最终要被转发的地址,lb指的是从nacos中按照名称获取微服务,并按照负载均衡策略
order: 1 #路由的优先级,数字越小代表路由的优先级越高
predicates: #断言(条件判断,返回值是boolean,转发请求要满足的条件)
- Path=/orderservice/**
filters:
- StripPrefix=1 #在请求转发之前去掉一层路径,这里去掉orderservice
discovery:
locator:
enabled: true #让gateway可以发现nacos中的微服务,这个必须配置
首先在运行我们的项目的时候访问订单微服务http://localhost:9200/order/2就可以获得订单信息,通过上述配置后我们通过网关访问http://localhost:7000/orderservice/order/2也可以获得订单信息,然后证明我们配置成功。其实在gateway中有一层默认的设置,当我们把yml中配置的路由全部注释掉后然后访问http://localhost:7000/provider/order/2依然可以获得订单信息,其中provider为订单微服务的名称,默认设置和我们的配置相对比就是把orderservice换成了provider微服务名称。
执行流程大体如下:
1.Gateway Client向Gateway Server发送请求
⒉.请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
3.然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
4.RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
5.如果过断言成功,由FilteringWebHandler创建过滤器链并调用
6.请求会一次经过PreFilter--微服务--PostFilter的方法,最终返回响应
这里摘用网上的相关内置断言工厂的描述
基于时间DateTime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
# 当前的请求必须要在下方指定的时间之后
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
# 当前的请求必须在下方指定的时间之前
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
# 当前的请求必须在下方指定的时间段之内
- Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]
基于Cookie的断言工厂
判断请求Cookie中必须某个key对应的value必须为指定的值
# cookie中chocolate的值必须为ch.p 第二个参数的值可以使用正则表达式
- Cookie=chocolate,ch.p
基于请求头的断言工厂
根据请求头中的某一个参数来进行匹配
# 必须包含X-Request-Id请求头,第二个参数的值可以使用正则表达式
- Header=X-Request-Id,\d+
基于域名的断言工厂
多个域名使用逗号分隔,支持通配符
- Host=**.somehost.org,**.anotherhost.org
基于请求方法的断言工厂
支持GET请求或者POST请求,中间使用逗号分隔
- Method=GET,POST
于Path路径的断言工厂
也就是我们快速入门案例中使用的,也支持通配符,如果有多个path中间使用逗号分隔
- Path=/order_server/**
除了通配符的方式,它还支持占位符的方式就比如rest接口中在结尾携带一个请求参数的方式,就比如/get/user/{id}
,这个id在路由匹配中肯定不能写死,
- Path=/red/{segment},/blue/{segment}
如果请求路径是,则此路由匹配,例如:/red/1 or /red/1/ 或 /red/blue or /blue/green。
Path Route Predicate Factory 有两个参数:一个 Spring 列表PathMatcher patterns和一个名为matchTrailingSlash(默认为true)的可选标志。如果matchTrailingSlash设置为false,则请求路径/red/1/将不匹配。
基于请求参数的断言工厂
根据请求参数中必须包含某个请求参数,或者某个请求参数的值是XXX,这个请求参数只能满足GET请求的方式在URL后面进行?key=value
拼接的方式
# 请求参数中包含green这个参数,则与前面的路由进行匹配
- Query=green
# 如果请求参数包含red其值与正则gree.表达式匹配的查询参数,则前面的路由匹配,因此red=green red=greet都会会匹配。
- Query=red,gree.
根据请求客户端Ip的断言工厂
例如,如果请求的远程地址是192.168.1.10
,则此路由匹配。
- RemoteAddr=192.168.1.1/24
根据权重
权重通常会有多个路由规则来组成,多个路由之间进行一个百分比的权重
权重需要配置两个参数,第一个参数是组名,第二个是权重值,多个路由如果是在一组中那么组名需要相同
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1,8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1,2
该路由会将约 80% 的流量转发到weighthigh.org,将约 20% 的流量转发到weightlow.org
自定义断言工厂实现
首先在配置文件中添加如下,表示参数age年龄的范围,在这之间符合要求。
gateway:
routes: #路由数组,指当请求满足什么样的条件转发到哪个微服务上
- id: order_router #当前路由标识,要求唯一
uri: lb://provider #请求最终要被转发的地址,lb指的是从nacos中按照名称获取微服务,并按照负载均衡策略
order: 1 #路由的优先级,数字越小代表路由的优先级越高
predicates: #断言(条件判断,返回值是boolean,转发请求要满足的条件)
- Path=/orderservice/**
- Age=18,60
filters:
- StripPrefix=1
自定义代码实现如下
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory {
//断言逻辑
public Predicate apply(AgeRoutePredicateFactory.Config config) {
return new Predicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String age = serverWebExchange.getRequest().getQueryParams().getFirst("age");
if(age!=null){
int myage = Integer.parseInt(age);
if(myage>config.minAge&&myage shortcutFieldOrder() {
return Arrays.asList("minAge","maxAge");//这个位置的顺序必须和配置文件中的顺序对应
}
//用于接收配置文件中的对应参数
@Data
@NoArgsConstructor
public static class Config{
private int minAge;
private int maxAge;
}
}
过滤器就是对于请求和响应做一些手脚。作用流程如下图:
从过滤器生命周期(影响时机点)的⻆度来说,主要有两个pre和post:
pre:这种过滤器在请求被路由之前调用。我们可以利用这类过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
post:这种过滤器在路由到微服务以后执行。这类过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端。
从过滤器作用范围的角度来说,可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。现在从作用范围划分的维度来讲解这两种filter。
区别:
GatewayFilter:网关过滤器,需要通过spring.cloud.routes.filters配置在具体的路由下,只作用在当前特定路由上,也可以通过配置spring.cloud.default-filters让它作用于全局路由上。
GlobalFilter:全局过滤器,不需要再配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain能够识别的过滤器。
内置局部过滤器
全局过滤器
内置的全局过滤器如下图:
自定义全局过滤器
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
开发中的鉴权逻辑:
1.当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
2.认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证·以后每次请求,客户端都携带认证的token
3.服务端对token进行解密,判断是否有效。
如下图:
自定义代码如下
@Component
@Order(-1)//越小优先级越高
@Slf4j
public class Filter implements GlobalFilter {
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(token==null||!token.equals("admin")){
//认证失败
log.error("认证失败");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}