API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求。如果让客户端直接与各个微服务通信,会有以下的问题:
以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性。
由于篇幅比较长, 决定分几篇来介绍相关知识:
consul 搭建可以参照官网,开箱即用。也可以参照 Docker 实战之 Consul 集群 基于 docker 环境搭建。
项目 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>
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>
配置文件 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; } }
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); } }
到这里基本环境就搭建完毕了,下节做过滤器的相关实现。感谢大家阅读,欢迎关注公众号【当我遇上你】学习交流。