您现在的位置是:首页 >技术交流 >Spring Cloud Gateway 服务网关的部署与使用详细介绍网站首页技术交流
Spring Cloud Gateway 服务网关的部署与使用详细介绍
为什么需要服务网关
传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。有了网关之后,网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务。
使用网关的好处
1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;
(2)降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性
(3)解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑
Gateway网关的demo
8001端口的服务,然后他有以下两个接口,但是我又不想让别人通过8001端口访问,我想让他通过9527访问怎么办?很简单通过Gateway搭建一个网关服务即可解决该问题。
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentMapper paymentMapper;
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentMapper.selectById(id);
log.info("*****查询结果:{}", payment);
if (payment != null) {
return new CommonResult(200, "查询成功, 服务端口:" + serverPort, payment);
} else {
return new CommonResult(444, "没有对应记录,查询ID: " + id + ",服务端口:" + serverPort, null);
}
}
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;
}
}
接下来搭建一个9527端口的Gateway入门级别的网关服务
1、以下是使用到的核心依赖,一般都会采用聚合工程,由父工程存放dependencyManagement当中的依赖,其他子模块引入使用的组件即可,单纯的练习图省劲的话,创建一个独立可运行的boot项目也可以。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
2, 添加配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
3、测试访问:http://localhost:9527/payment/get/1
Gateway三大核心概念
Route(路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
请求从外面过来,会先被网关处理,网关会决定该请求会调用哪个微服务,但是这个请求怎么调用呢?所以网关是不是必须要有一个路由器啊,它要可以进行请求的路由转发,具体决定该请求会调用哪个微服务。
Predicate(断言)
是什么
启动网关服务的时候会发现有一排日志,如下所示:
首先我们会发现一共有12个 RoutePredicateFactorie,他们每个都有自己的应用场景
作用
如果请求与断言相匹配则进行路由,如果不匹配直接404
路由断言规则
时间作为匹配路由规则
- After
当请求的时间在断言时间之后,将匹配路由
也就是我们下面配置的时间 2022-03-20T21:02:47.789 之后可以成功匹配路由,之前匹配失败
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- After=2022-03-20T21:02:47.789-07:00[Asia/Shanghai]
当请求的时间在断言时间之后,将匹配路由
也就是我们配置的时间 2022-03-20T21:02:47.789 之后可以成功匹配路由
-
Before
与After正好相反 -
Between
指定两个时间,用逗号分割,如果请求时间在这两个时间之间,将匹配路由
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Between=2022-03-19T21:02:47.789-07:00[Asia/Shanghai],2022-03-22T21:02:47.789-07:00[Asia/Shanghai]
Cookie作为匹配路由规则
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Cookie=token,123
如果请求cookie中有name为token,且值为123将匹配当前路由
name和value有一个不一样都不能成功路由
name和value都相同 则能成功路由
请求头作为匹配路由规则 Header
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Header=token,123
请求对象的请求头中 如果有name为token,且值为123,将匹配当前路由
如图 测试请求头没有name为token value为123 的请求头信息 则不能匹配路由
当请求头中有 name为token 值为 123的请求头信息时,能匹配到当前路由
Host作为匹配路由规则 Host
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Host=**.haha.com:81
请求方法作为匹配路由规则 Method
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Method=GET,POST
上面的请求GET和POST请求都能匹配到路由,如果我们换成PUT请求则不能匹配到路由
路径作为匹配路由规则 Path
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Path=/say
上面的案例访问 /say 能匹配到路由
多加一级路径则不能匹配到路由
可以正则 改成 /say/**
此时无论访问 /say 还是 /say/one都能匹配到路由
查询参数作为匹配路由规则 Query
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Query=skuID
如果只写一个参数 则意思为 查询参数有skuID则匹配当前路由
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8080 # 匹配后提供服务的路由地址
predicates:
- Query=skuID,11
如果两个参数,用逗号分割 则意思为 查询参数为skuID ,且值为11 匹配当前路由
注意 两个条件都必须满足 且请求方式与服务请求映射的方式一致
如果skuID 不为 11 也不能匹配到当前路由
权重作为匹配路由规则 Weight
server:
port: 81
spring:
cloud:
gateway:
routes: # 配置路由,是一个集合
- id: apptest1 # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:80 # 匹配后提供服务的路由地址
predicates:
- Path=/say/**
- Weight=group,5
- id: apptest2 # 路由的ID, 没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8081 # 匹配后提供服务的路由地址
predicates:
- Path=/say/**
- Weight=group,5
Filter(过滤)
过滤器可以在执行方法前和执行方法后进行过滤,所谓的过滤就是可以在请求上加一些操作,例如匹配到路由后可以在请求上添加个请求头,或者参数等等。
Gateway过滤器分为了两种:路由过滤器 和 全局过滤器
路由过滤器
全局过滤器
全局过滤器的作用是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
自定义全局过滤器(Filter)
Gateway内部有一个接口 名为GlobalFilter,这个就是Gateway的全局过滤器接口,只要在应用中实现此接口后注册为Spring的Bean,它就会就会帮我们将这个实现注册到全局过滤器链条里边去
测试:
请求中必须要包含uname请求参数,那么才会进行路由转发,否则不会进行路由转发,如下图:
过滤器的执行顺序
过滤器会被执行两次,过滤分为pre和post。
pre:请求前调用。
post:响应结果返回时调用,顺序和pre完全相反。
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序规则如下:
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。