spring-cloud-gateway简介
概述
API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求。如果让客户端直接与各个微服务通信,会有以下的问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,每个服务都需要独立认证。
- 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
- 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。
以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性。
由于篇幅比较长, 决定分几篇来介绍相关知识:
- spring-cloud-gateway 简介+基本环境搭建
- spring-cloud-gateway 过滤器+审计
- spring-cloud-gateway 静态路由
- spring-cloud-gateway 动态路由
- spring-cloud-gateway 限流+过载保护
- spring-cloud-gateway 认证
- spring-cloud-gateway 鉴权
环境参数
- 开发工具: IDEA
- 基础工具: Maven+JDK8
- SpringBoot 版本: 2.1.4.RELEASE
- SpringCloud 版本: Greenwich.SR3
- 服务发现: consul
consul 搭建可以参照官网,开箱即用。也可以参照Docker 实战之 Consul 集群 基于 docker 环境搭建。
SpringCloud 项目搭建
项目 pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.idea360</groupId>
<artifactId>spring-cloud-learning</artifactId>
<version>1.0</version>
<modules>
<module>idc-provider1</module>
<module>idc-provider2</module>
<module>idc-gateway</module>
</modules>
<packaging>pom</packaging>
<name>spring-cloud-learning</name>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<name>cuishiying</name>
<email>cuishiying163@163.com</email>
</developer>
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<spring-boot.version>2.1.4.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
<docker.image.prefix>csy</docker.image.prefix>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.name}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<skipTests>true</skipTests> <!--默认关掉单元测试 -->
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<!--阿里云主仓库,代理了maven central和jcenter仓库-->
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--阿里云代理Spring 官方仓库-->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://maven.aliyun.com/repository/spring</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<!--阿里云代理Spring 插件仓库-->
<pluginRepository>
<id>spring-plugin</id>
<name>spring-plugin</name>
<url>https://maven.aliyun.com/repository/spring-plugin</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
服务提供者 1(服务提供+服务消费)
pom.xml
<?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>spring-cloud-learning</artifactId>
<groupId>cn.idea360</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>idc-provider1</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件 application.yml
spring:
application:
name: idc-provider1
cloud:
consul:
host: localhost
port: 8500
server:
port: 2001
feign:
client:
config:
remote-service: #服务名,填写default为所有服务
connectTimeout: 1000
readTimeout: 3000
SpringBoot 项目入口 Provider1App.java
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Provider1App {
public static void main(String[] args) {
SpringApplication.run(Provider1App.class, args);
}
}
远程调用 RemoteService.java
@FeignClient("idc-provider2")
public interface RemoteService {
/**
* 方法名随意,url路径匹配即可
* @return
*/
@GetMapping("/provider2")
Object getProvider2();
}
web 服务
@RestController
public class TestController {
@Autowired
Environment env;
@Autowired
RemoteService remoteService;
@GetMapping("/provider1/{id}")
public Object getTest(@PathVariable(required = false) Integer id, @RequestParam(required = false) String username) {
return env.getProperty("local.server.port");
}
@PostMapping("/provider1")
public Object postTest(@RequestBody Map<String, String> params) {
System.out.println(params);
return env.getProperty("local.server.port");
}
@GetMapping("/remote/get")
public Object remoteGetTest() {
Object provider2 = remoteService.getProvider2();
System.out.println("remote load data from provider2:" + provider2);
return provider2;
}
}
服务提供者 2(服务提供)
provider2 和 provider1 类似,这里只改写下 web 实现类
@RestController
public class TestController {
@Autowired
Environment env;
@GetMapping("/provider2")
public Object getTest() {
String port = env.getProperty("local.server.port");
JSONObject jsonObject = new JSONObject();
jsonObject.put("port", port);
System.out.println("idc-provider2:" + port);
return jsonObject;
}
}
Feign 中集成了 Ribbon负载均衡。这里可以用不同端口启动 2 个 provider2 实例, 然后通过 provider1 调用 provider2,可以看到轮询输出不同的端口。
网关服务
pom.xml
<?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>spring-cloud-learning</artifactId>
<groupId>cn.idea360</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>idc-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--consul客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--健康监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 2000
spring:
application:
name: idc-gateway
redis:
host: localhost
port: 6379
timeout: 6000ms # 连接超时时长(毫秒)
jedis:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
cloud:
consul:
host: localhost
port: 8500
gateway:
discovery:
locator:
enabled: true # gateway可以通过开启以下配置来打开根据服务的serviceId来匹配路由,默认是大写
routes:
- id: provider1 # 路由 ID,保持唯一
uri: lb://idc-provider1 # uri指目标服务地址,lb代表从注册中心获取服务
predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
- Path=/p/**
filters:
- StripPrefix=1 # 过滤器StripPrefix,作用是去掉请求路径的最前面n个部分截取掉。StripPrefix=1就代表截取路径的个数为1,比如前端过来请求/test/good/1/view,匹配成功后,路由到后端的请求路径就会变成http://localhost:8888/good/1/view
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty.http.client: DEBUG
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApp {
public static void main(String[] args) {
SpringApplication.run(GatewayApp.class, args);
}
}
结语
到这里基本环境就搭建完毕了,下节做过滤器的相关实现。感谢大家阅读,欢迎关注公众号【当我遇上你】学习交流。