在传统的单体架构中,我们只需要开放一个服务给客户端调用即可。
但是微服务架构中是将一个系统拆分成多个微服务,不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至百个地址,这对于客户端方来说太复杂也难以维护,而且一不小心就会出错,这在开发测试中深有体会。然而有了网关就不一样了:网关作为系统的唯一流量入口,所有请求都必须先经过网关,由网关将请求路由到合适的微服务。网关有以下几点好处:
交互更简单:减少了客户端与各个微服务之间的交互次数
授权认证:在网关上进行认证,再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
安全 :只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
官网:spring-cloud-gateway
gateway历史背景?(了解)
Spring Cloud全家桶中有一个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关。但是在2.x版本中,Zuul的升级一直跳票,迟迟不发布,Spring Cloud最后自己研发了一个网关替代Zuul,就是Gateway。
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,限流。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
gateway特性?
基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建
动态路由:能够匹配任何请求属性
可以对路由指定 Predicate(断言)和 Filter(过滤器)
集成Hystrix的断路器功能
集成 Spring Cloud 服务发现功能
易于编写的 Predicate(断言)和 Filter(过滤器)
请求限流功能
支持路径重写
为什么选择gateway作为网关?
从商业方面来说,一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。另一方面虽然Netflix早就发布了最新的 Zuul 2.x,但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期,不知前景如何?
从技术层面来说,Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。而且很多功能Zuul都没有用起来,而gateway用起来非常的简单便捷。
多方面综合考虑Gateway是很理想的网关选择。
gateway工作原理
客户端向 Spring Cloud Gateway 发出请求。 如果网关处理程序映射确定请求与路由匹配,则将其发送到网关 Web 处理程序。 此处理程序通过特定于请求的过滤器链运行请求。 过滤器用虚线划分的原因是过滤器可以在发送代理请求之前和之后运行逻辑。 执行所有“预”过滤器逻辑。 然后发出代理请求。 发出代理请求后,将运行“发布”过滤器逻辑。
路由是构建网关的基本模块,它由ID,目标URI,断言集合和过滤器集合组成,如果断言为true,则转发到该路由。
id:路由标识,要求唯一,名称任意(建议配合服务名)
uri:请求最终被转发到的目标地址
order: 路由优先级,数字越小,优先级越高
predicates:断言数组(判断条件),路径相匹配的进行路由
filters:过滤器数组,可以在返回请求之前或之后时修改请求和响应的内容。
断言(Predicate)是基于Spring WebFlux的HandlerMapping实现的。Gateway包含了很多路由断言工厂(spring-cloud-gateway内置的断言),并且这些工厂对应着HTTP请求的很多属性进行了处理,当客户端发送HTTP请求时,HandlerMapping会获取请求参数,并与Gateway中配置的Predicates进行比对,若满足规则就按规则约定进行路由放行,否则拒绝访问或报404错误。
上述断言的种类有十一种,这里以其中几种为例介绍一下如何配置使用:
spring:
cloud:
gateway:
routes: # 路由数组,当请求满足什么样的断言时,转发到哪个微服务上
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后被转发的路由地址
# 设置断言
predicates:
# Path Route Predicate Factory 断言,满足/payment/get/** 路径的请求都会被路由到 http://localhost:8001 这个uri中
- Path=/payment/get/**
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后被转发的路由地址
# 设置断言
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
- After=2023-01-08T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
spring:
cloud:
gateway:
routes: # 路由数组,当请求满足什么样的断言时,转发到哪个微服务上
- id: gateway-provider_1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后被转发的路由地址
# 设置断言
predicates:
# Path Route Predicate Factory 断言,满足 /gateway/provider/** 路径的请求都会被路由到 http://localhost:8001 这个uri中
- Path=/gateway/provider/**
# Weight Route Predicate Factory 断言,同一分组按照权重进行分配流量,这里分配了80%
# 第一个group1是分组名,第二个参数是权重
- Weight=group1, 8
- id: gateway-provider_2
uri: http://localhost:8002
predicates:
- Path=/gateway/provider/**
# Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
- Weight=group1, 2
经过上面的一系列配置我们可以发现Spring Cloud Gateway 中的断言命名有这样一个公式:“xxx + RoutePredicateFactory”,比如“请求时间满足配置时间之后”断言 AfterRoutePredicateFactory,那么配置时直接取前面的 “After”。
如果路由转发匹配到了两个或以上,则是的按照配置先后顺序转发,上面都配置了路径:“ Path=/gateway/provider/** ”,如果没有配置权重,则肯定是先转发到 “http://localhost:8001”,但是如果在相同分组内设置了权重,则按照权重比例进行流量分配。
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring-Cloud-Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生,下面介绍一下常用路由过滤器的用法:
官网:Spring-Cloud-Gateway中内置GatewayFilter
(1)Filter 过滤器的生命周期:
PRE:在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
(2)Filter 从作用的范围一般分为两种:
局部过滤器 GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置)
全局过滤器 GlobalFilter:应用到所有的路由上(无需配置,全局生效)
(3)过滤器命名规则:xxx + GatewayFilterFactory
(4)局部过滤器 GatewayFilter
AddResponseHeaderGatewayFilterFactory ,在响应头中添加一个键值对
spring:
cloud:
gateway:
routes:
- id: gateway-provider_1
uri: http://localhost:8001
predicates:
- Path=/gateway/provider/**
# 配置局部过滤器
filters:
- AddResponseHeader=X-Response-Foo, Bar
StripPrefixGatewayFilterFactory,对指定数量的路径前缀进行去除的过滤器
spring:
cloud:
gateway:
routes:
- id: strip_prefix_route
uri: http://localhost:8201
predicates:
- Path=/user-service/**
filters:
- StripPrefix=2
以上配置会把以/user-service/开头的请求的路径去除两位,通过curl工具使用以下命令进行测试:
原始请求:curl http://localhost:9201/user-service/a/user/1
变为:curl http://localhost:8201/user/1
PrefixPath GatewayFilter,对原有路径进行增加操作的过滤器。
spring:
cloud:
gateway:
routes:
- id: prefix_path_route
uri: http://localhost:8201
predicates:
- Method=GET
filters:
- PrefixPath=/user
以上配置会对所有GET请求添加/user路径前缀,通过curl工具使用以下命令进行测试。
原有请求:curl http://localhost:9201/1
变为:curl http://localhost:8201/user/1
Hystrix GatewayFilter,开启断路器功能,并提供服务降级处理
Hystrix 过滤器允许你将断路器功能添加到网关路由中,使你的服务免受级联故障的影响,并提供服务降级处理。
要开启断路器功能,我们需要在pom.xml中添加Hystrix的相关依赖:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
添加相关服务降级的处理类:
@RestController
public class FallbackController {
@GetMapping("/fallback")
public Object fallback() {
Map result = new HashMap<>();
result.put("data",null);
result.put("message","Get request fallback!");
result.put("code",500);
return result;
}
}
在配置文件中添加相关配置,当路由出错时会转发到服务降级处理的控制器上:
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: http://localhost:8201
predicates:
- Method=GET
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
关闭user-service,调用该地址进行测试:http://localhost:9201/user/1 ,发现已经返回了服务降级的处理信息。
AddRequestParameter GatewayFilter,给请求添加参数的过滤器
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://localhost:8201
filters:
- AddRequestParameter=username, macro
predicates:
- Method=GET
以上配置会对GET请求添加username=macro的请求参数,相当于发起该请求:
curl http://localhost:8201/user/getByUsername?username=macro
要添加过滤器并将其应用于所有路由,您可以使用spring.cloud.gateway.default-filters. 此属性采用过滤器列表。以下清单定义了一组默认过滤器:
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
(6)GlobalFilter 全局过滤器:
全局过滤器应用全部路由上,无需开发者配置,Spring Cloud Gateway 也内置了一些全局过滤器(spring-cloud-gateway内置的global-filters):
GlobalFilter 的功能其实和 GatewayFilter 是相同的,只是 GlobalFilter 的作用域是所有的路由配置,而不是绑定在指定的路由配置上。多个 GlobalFilter 可以通过 @Order 或者 getOrder() 方法指定执行顺序,order值越小,执行的优先级越高。
注意:由于过滤器有 pre 和 post 两种类型,pre 类型过滤器如果 order 值越小,那么它就应该在pre过滤器链的顶层,post 类型过滤器如果 order 值越小,那么它就应该在 post 过滤器链的底层。示意图如下:
当请求与路由匹配时,过滤 Web 处理程序会将所有实例GlobalFilter和所有特定路由的实例添加GatewayFilter到过滤器链中。这个组合的过滤器链是按org.springframework.core.Ordered接口排序的,可以通过实现getOrder()方法来设置。
由于 Spring Cloud Gateway 区分过滤器逻辑执行的“前”和“后”阶段,具有最高优先级的过滤器是“前”阶段的第一个和“后”阶段的最后一个阶段。
@Component
@Log4j2
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String username = exchange.getRequest().getQueryParams().getFirst("username");
if (username == null) {
log.error("用户名为null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 得到权重,order越小,优先级越高
*
* @return int
*/
@Override
public int getOrder() {
return 0;
}
}
当不带username参数时网关会路由失败:
service-url:
user-service: http://localhost:8081
spring:
cloud:
gateway:
routes:
- id: path_route
uri: ${service-url.user-service}/user/get/{id}
predicates:
- Path=/user/get/{id}
这里存在一个问题,在微服务集群部署中一个服务可能会有多台主机,我们这样配置路由不够灵活,每更改一次服务的主机信息都要重新编写一次配制文件,然后还需要重启Gateway服务器。我们要知道,在真正的项目中重启服务是很耗时的,我们应该尽量避免这种情况
同时网关服务gateway需要知道所有服务的域名或IP地址以及端口,另外,一旦服务的域名或IP地址发生修改,路由配置中的 uri 就必须修改。
此外在服务集群中无法实现负载均衡。
当出现这些问题时,我们可以集成到注册中心,使得网关能够从注册中心自动获取uri,并实现负载均衡,同时Spring Cloud Gateway提供了lb//服务名的方式来动态的配置路由,网关会根据注册中心的服务名动态获取服务的URL,这样即便该服务的某一台主机地址改变或者挂掉,网关都不必再跟着改变:
由于 Netflix Ribbon 进入停更维护阶段,因此 SpringCloud 2020.0.1 版本之后 删除了eureka中的ribbon,替代ribbon的是spring cloud自带的LoadBalancer,默认使用的是轮询的方式 。新版本的 Nacos discovery 都已经移除了 Ribbon ,此时我们需要引入 loadbalancer 代替,才能调用服务提供者提供的服务。
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.cloud
spring-cloud-starter-bootstrap
org.springframework.cloud
spring-cloud-starter-loadbalancer
org.springframework.boot
spring-boot-starter-webflux
org.springframework.cloud
spring-cloud-starter-gateway
@EnableDiscoveryClient //开启服务注册和发现
bootstrap.yml,主要是对Nacos配置中心的一些设置
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos地址
config:
server-addr: localhost:8848 #Nacos地址
file-extension: yaml #由于在nacos配置中心创建的配置是yaml格式的,所以在此指定
application.yml,启用对应环境
server:
port: 80
spring:
profiles:
active: dev
在Nacos后台编写对应的配制文件,dataId为gateway-service.yaml
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
default-filters:
# 保留唯一的,出现重复的属性值,会保留一个。例如有两个my:bbb的属性,最后会留一个。
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
globalcors:
cors-configurations:
'[/**]':
allowedHeaders: '*'
allowedMethods: '*'
allowedOrigins: '*'
routes:
- id: path_route
uri: lb://user-service
predicates:
- Path=/user/get/{id}
##################省略数据库,redis等其他配置#########################
在Nacos配置中心添加新的路由
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
globalcors:
cors-configurations:
'[/**]':
allowedHeaders: '*'
allowedMethods: '*'
allowedOrigins: '*'
routes:
- id: path_route
uri: lb://user-service
predicates:
- Path=/user/get/{id}
- id: userservice_route
uri: http://localhost:8080
predicates:
- Path=/user/test
##################省略数据库,redis等其他配置#########################