第七章 服务网关Gateway
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
-
客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
-
认证复杂,每个服务都需要独立认证。
-
微服务做集群的情况下,客户端并没有负责均衡的功能
上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示:
网关是如何知道微服务的地址?网关如何进行负载均衡呢?
网关需要将自己的信息注册到注册中心上并且拉取其他微服务的信息,然后再调用的时候基于Ribbon实 现负载均衡
7.1 常见网关介绍
- Nginx+lua
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用,lua是一种脚本语言,可以来编 写一些简单的逻辑, nginx支持lua脚本。
- Kong
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:只支 持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
- Zuul
Netflflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配置;依赖 组件较多;处理Http请求依赖的是Web容器,性能不如Nginx,Spring Cloud Gateway。
Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。
注意: SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关。
7.2 Gateway简介
Spring Cloud Gateway是Spring公司基于Spring 5.0, Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如: 安全,监控和限流。
优点:
-
性能强劲:是第一代网关Zuul的1.6倍
-
功能强大:内置了很多实用的功能,例如转发、监控、限流等
-
设计优雅,容易扩展
缺点:
-
其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
-
不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
-
需要Spring Boot 2.0及以上的版本,才支持
7.3 Gateway快速入门
- 创建⼀个 api-gateway 的模块,导⼊相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shop-parent</artifactId>
<groupId>com.zgh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
- 编写启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayServerApplication.class, args);
}
}
- 编写配置文件
server:
port: 9000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
- 启动测试
7.4 自定义路由规则
- 在application.yml中添加新的路由规则
spring:
cloud:
gateway:
routes:
- id: product_route
uri: lb://product-service
predicates:
- Path=/product-serv/**
filters:
- StripPrefix=2,name
- id: order_route
uri: lb://order-service
predicates:
- Path=/order-serv/**
filters:
- StripPrefix=1
- 启动测试
7.5 Gateway概念
路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:
-
id,路由标识符,区别于其他 Route。
-
uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
-
order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
-
predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
-
filter ,过滤器用于修改请求和响应信息。
执行流程图:
7.6 过滤器Filter
过滤器就是在请求的传递过程中,对请求和响应做一些手脚.
在Gateway中, Filter的生命周期只有两个: “pre” 和 “post”。
-
PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
-
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
在Gateway中, Filter的作用范围两种:
-
GatewayFilter:应用到单个路由或者一个分组的路由上。
-
GlobalFilter:应用到所有的路由上
7.6.1 局部路由过滤器
局部过滤器是针对单个路由的过滤器,在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器, 有兴趣同学可以自行了解,这里太多了,我们就不一一讲解,我们主要来讲一下自定义路由过滤器。
需求: 统计订单服务调用耗时.
-
编写Filter类,注意名称是有固定格式xxxGatewayFilterFactory
package com.zgh.filters; @Component public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterFactory.Config> { private static final String BEGIN_TIME = "beginTime"; //构造函数 public TimeGatewayFilterFactory() { super(TimeGatewayFilterFactory.Config.class); } //读取配置⽂件中的参数 赋值到 配置类中 @Override public List<String> shortcutFieldOrder() { return Arrays.asList("show"); } @Override public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //前置逻辑 if (!config.show) { return chain.filter(exchange); } exchange.getAttributes().put(BEGIN_TIME, System.currentTimeMillis()); //后置逻辑 return chain.filter(exchange).then(Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(BEGIN_TIME); if (startTime != null) { System.out.println(exchange.getRequest().getURI() + "请求耗时: " + (System.currentTimeMillis() - startTime) + "ms"); } })); } }; } @Setter @Getter static class Config { private boolean show; } }
-
在指定的路由中添加路由规则(- Time=true)
server:
port: 9000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
routes:
- id: product_route
uri: lb://product-service
predicates:
- Path=/product-serv/**
filters:
- StripPrefix=2,name
- id: order_route
uri: lb://order-service
predicates:
- Path=/order-serv/**
filters:
- StripPrefix=1
- Time=true
- 访问商品服务的时候是没有打印日志的,访问订单服务的时候打印入职如下:
7.6.2 全局路由过滤器
全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
需求: 实现统一鉴权的功能,我们需要在网关判断请求中是否包含token且,如果没有则不转发路由,有则 执行正常逻辑。
-
编写全局过滤类
package com.zgh.filters; @Component public class AuthGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (StringUtils.isBlank(token)) { System.out.println("鉴权失败"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } }
-
启动并测试
7.7 集成Sentinel实现网关限流
网关是所有请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们本次采用前面学过的Sentinel组件来实现网关的限流。 Sentinel支持对SpringCloud Gateway 、Zuul等主流网关进行限流。
从1.6.0版本开始, Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:
-
route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId
-
自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组
7.7.1 网关集成Sentinel
https://github.com/alibaba/Sentinel/wiki/网关限流
-
添加依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>
-
添加配置
spring:
cloud:
sentinel:
transport:
port: 9999
dashboard: localhost:8080
- 重启网关服务并测试.
7.7.2 API分组
Sentinel中支持按照API分组进行限流,就是我们可以按照特定规则进行限流.
在管控台页面中提供了三种方式的API分组管理
-
精准匹配
-
前缀匹配
-
正则匹配
现在我们定义了如下的接口地址
@RestController
@RequestMapping("/v1")
public class TestController {
@RequestMapping("/test1")
public String test1() {
return "test1";
}
@RequestMapping("/test2")
public String test2() {
return "test2";
}
@RequestMapping("/test3/test")
public String test3() {
return "test3";
}
}
精准匹配
1.在API管理中新建API分组,匹配模式选择精准匹配,匹配串写请求URL地址
2.在流控规则中, API类型中选择API分组,然后在API名称中选择我们刚刚定义的V1限流
3.此时上面三个请求中,只有 /product-service/v1/test1会被限流
前缀匹配
1.在API管理中新建API分组,匹配模式选择前缀匹配,匹配串写请求URL地址
此时 /order-serv/v1/test1 和 /order-serv/v1/test2 会被限流
注意: 如果路径为/*表示匹配一级路径,如果路径为/**表示多级路径
正则匹配
1.在API管理中新建API分组,匹配模式选择正则匹配,匹配串写请求URL地址
此时所有路径都会被限流
7.7.3 修改限流默认返回格式
- 在配置类GatewayConfiguration.java中添加如下配置
package com.zgh.config;
public class GatewayConfiguration {
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange
serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接⼝被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON).
body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
- 重启并测试
引文来源:https://www.bilibili.com/video/BV19T411M787