阅读 27

Spring-Cloud-Gateway简介

概述

API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求。如果让客户端直接与各个微服务通信,会有以下的问题:

  1. 客户端会多次请求不同的微服务,增加了客户端的复杂性。
  2. 存在跨域请求,在一定场景下处理相对复杂。
  3. 认证复杂,每个服务都需要独立认证。
  4. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
  5. 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。

以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性。

由于篇幅比较长, 决定分几篇来介绍相关知识:

  1. spring-cloud-gateway 简介+基本环境搭建
  2. spring-cloud-gateway 过滤器+审计
  3. spring-cloud-gateway 静态路由
  4. spring-cloud-gateway 动态路由
  5. spring-cloud-gateway 限流+过载保护
  6. spring-cloud-gateway 认证
  7. 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>
        <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>
        <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>
        <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);
    }

}
复制代码

结语

到这里基本环境就搭建完毕了,下节做过滤器的相关实现。感谢大家阅读,欢迎关注公众号【当我遇上你】学习交流。