1.环境准备

本篇文章我们将实现下图所示的架构
在这里插入图片描述
我们可以接着使用前面《SpringCloud之Feign使用篇》中的项目环境,当然也也可以自己重新搭建都是可以的,这里我们使用的springcloud的版本是:Greenwich.RELEASE

  1. 注册中心我们使用Eureka ,为两个实例,9090与9091
  2. 用户服务 spring-cloud-user-service-consumer ,也是两个实例,8080与8081
  3. 订单服务 spring-cloud-order-service-provider ,也是两个实例,7070与7071
  4. 服务网关使用的是springcloud gateway组件 端口是8020
  5. 再就是nginx作为我们项目入口,为80端口

2.简单使用

2.1 网关项目的搭建

新建module spring-cloud-gateway-server

2.1.1 依赖

需要我们在spring-cloud-gateway-server 项目pom.xml添加依赖,这里 父工程我没有使用之前的,而是选择了spring-boot,因为在springcloud gateway中,web使用的webflux 没有使用spring mvc ,而之前的父工程中引入mvc。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <dependencyManagement>
        <!--spring cloud依赖版本管理-->
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>
        <!--eureka 客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--gateway 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--webflux 依赖 ,gateway 使用的是webflux,并没有使用springmvc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <!--springboot监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2.1.2 配置

在网关服务 application.yml 中配置网关路由规则

server:
  port: 8020
spring:
  application:
    name: spring-cloud-gateway-server
  cloud:
    gateway:

      # 配置路由,可以配置多个
      routes:

        - id: order-microservice-router  # id 自定义路由的id
          uri: http://127.0.0.1:7070   # uri就是 目标服务地址
          predicates:   # 断言,也就是路由条件 ,这里使用了path作为路由条件
            - Path=/order/**
        - id: user-microservice-router
          uri: http://127.0.0.1:8081
          predicates:
            - Path=/user/**
#eureka 配置
eureka:
  client:
    service-url:
      # eureka server url
      defaultZone: http://EurekaServerA:9090/eureka,http://EurekaServerB:9091/eureka
    register-with-eureka: true
    fetch-registry: true
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

其中我们配置了2个路由,path=/order 开头的转到http://127.0.0.1:7070 服务上,也就是咱们的订单服务,path=/user 开头的转到http://127.0.0.1:8081 服务上,也就是我们的用户服务。

2.2 启动并测试

我们首先将 Eureka Server集群 9090与9091提起来,然后再将用户服务与订单服务提起来,在起订单服务之前为了测试方便 需要给订单服务里面添加个接口:
我们在订单服务spring-cloud-order-service-provider 的controller里面添加个接口(之前的getTodayFinishOrderNum方法不用动,用户服务还要调用它)

@RestController
@RequestMapping("/order/data")
public class OrderStatisticServiceController {
    @Value("${server.port}")
    private Integer port;

    @Value("${spring.application.name}")
    private String appName;
    /**
     * 根据用户id获取今日完单数
     * @param id 用户ID
     * @return  完单数
     */
    @GetMapping("/getTodayFinishOrderNum/{id}")
    public Integer getTodayFinishOrderNum(@PathVariable("id") Integer id){
        System.out.println("我是"+port);
        if (port==7070){
            try {// 睡眠10s
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return port;
    }
    // 获取appName
    @GetMapping("/getAppName/{id}")
    public String  getAppName(@PathVariable("id") Integer id){
        return appName;
    }
}

然后将服务提起来,可以看到已经全部起来了,将gateway项目也提起来。
在这里插入图片描述
浏览器请求:http://127.0.0.1:8020/order/data/getAppName/100 ,注意咱们这个ip与port是网关服务的
在这里插入图片描述
这时候可以看到网关将咱们的请求打到order服务上了。

浏览器再请求:http://127.0.0.1:8020/user/data/getTodayStatistic/100
在这里插入图片描述
网关就把咱们这次请求打到user服务上了。

3.路由规则

在springcloud gateway 中内置了很多 Predicates 功能,实现了很多路由匹配规则匹配到对应的路由。

断言类型描述
DateTime 时间断言根据请求时间在配置的时间的前面,后面,之前
Cookie 类断言指定Cookie正则匹配指定值
Header 请求头指定Header正则匹配指定值,请求头中是否包含某个属性
Host请求主机断言请求Host匹配指定值
Method请求方式断言请求Method匹配指定请求方式
Path 请求路径类断言请求路径正则匹配指定值
QueryParam 请求参数查询参数正则匹配指定值
RemoteAddr远程地址类断言请求远程地址匹配指定值

4.动态路由

在上面第二节的简单使用中,我们把uri 这个目标服务器写死了,固定到了某个ip:port上面,其实springcloud gateway支持从注册中心获取服务列表进行访问,能够实现动态路由。下面我们就演示下动态路由

4.1 网关服务修改
4.1.1 配置文件修改

这里我们需要把uri 配置成 lb://在注册中心注册的服务名

server:
  port: 8020
spring:
  application:
    name: spring-cloud-gateway-server
  cloud:
    gateway:

      # 配置路由,可以配置多个
      routes:
        - id: order-microservice-router  # id 自定义路由的id
          # uri就是目标服务地址,这里使用服务名的方式,gateway会帮我们去注册中心中获取服务列表
          uri: lb://spring-cloud-order-service-provider
          predicates:   # 断言,也就是路由条件 ,这里使用了path作为路由条件
            - Path=/order/**
        - id: user-microservice-router
          uri: lb://spring-cloud-user-service-consumer
          predicates:
            - Path=/user/**
#eureka 配置
eureka:
  client:
    service-url:
      # eureka server url
      defaultZone: http://EurekaServerA:9090/eureka,http://EurekaServerB:9091/eureka
    register-with-eureka: true
    fetch-registry: true
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
4.1.2 项目启动类修改

在项目启动类上面添加@EnableDiscoveryClient的注解,表示开启Eureka
在这里插入图片描述

4.2 重启并测试

重启网关服务,在测试前,我们为了测试方便还是需要修改订单服务的controller方法,将端口加到返回值中。

@RestController
@RequestMapping("/order/data")
public class OrderStatisticServiceController {
    @Value("${server.port}")
    private Integer port;
    @Value("${spring.application.name}")
    private String appName;
    /**
     * 根据用户id获取今日完单数
     * @param id 用户ID
     * @return  完单数
     */
    @GetMapping("/getTodayFinishOrderNum/{id}")
    public Integer getTodayFinishOrderNum(@PathVariable("id") Integer id){
        System.out.println("我是"+port);
        if (port==7070){
            try {// 睡眠10s
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return port;
    }
    // 获取appName
    @GetMapping("/getAppName/{id}")
    public String  getAppName(@PathVariable("id") Integer id){
        // appName +port
        return appName+String.valueOf(port);
    }
}

我们重启下订单服务,然后进行测试。
浏览器访问:http://127.0.0.1:8020/order/data/getAppName/100,多访问几次
在这里插入图片描述
在这里插入图片描述
我们可以看到有7070的也有7071的,说明我们配置的动态路由生效了。

5.过滤器

5.1 过滤器介绍

在springchoud gateway中 过滤器可以分为Global过滤器与Gateway过滤器

过滤器类型说明
Global过滤器这种就是作用在所有的路由上
Gateway过滤器这种就是作用在指定的路由上面

然后根据过滤器的生命周期分可以分为 pre过滤与post过滤

生命周期点介绍
pre这个就是请求来的时候,可以对请求做一些动作,我们可利用这种过滤器实现身份验证,在集群中选择请求的微服务,记录调试信息等。
post这个就是响应过来的时候,可以对响应做某些过滤动作,这种过滤器可用来为响应添加标准的 HTTP Header,收集统计信息和指标,将响应从微服务发送给客户端等。
5.2自定义过滤器

我们这里自定义一个全局过滤器,然后实现一个黑白名单的功能。
这里我们写一个BlackListFilter类然后实现GlobalFilter, Ordered 接口

@Component
public class BlackListFilter  implements GlobalFilter, Ordered {

    // 这里模拟下黑名单
    private static final List<String>  blackList=new ArrayList<>();
    static {

        blackList.add("127.0.0.1");// 模拟本机地址
    }
    /**
     * 进行过滤操作
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取请求
        ServerHttpRequest request = exchange.getRequest();

        // 获取响应
        ServerHttpResponse response = exchange.getResponse();
        // 获取客户端ip
        String host = request.getRemoteAddress().getHostString();
        System.out.println("remote host:"+host);
        if (blackList.contains(host)){  // 这个客户端ip在黑名单里面
            // 设置响应码
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            String data = "拒绝访问";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
            HttpHeaders headers = response.getHeaders();
            // 设置中文乱码
            headers.add("content-type", "text/html;charset=utf-8");
            return response.writeWith(Mono.just(wrap));
        }
        // 放行到下一个过滤器
        return chain.filter(exchange);
    }

    /**
     * 主要是多个过滤器的时候,需要对过滤器排序,
     * 先经过哪个,后经过哪个,数值越小,这个优先级越高
     * @return order优先级
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

实现这个GlobalFilter 接口主要是说明我这个是个全局过滤器并在定义一些过滤的动作,
然后实现这个Ordered 接口主要是对咱们自定义过滤器的优先级做一个设置,数值越小,filter就越早执行,优先级就越高
重启网关项目,然后测试一下:
浏览器输入http://127.0.0.1:8020/order/data/getAppName/100 ,可以看到请求被拦截了。
在这里插入图片描述

6.高可用

我们要想对网关做高可用,就得依赖网关上层的nginx来实现,接下来我们简单配置下nginx 来实现gateway的高可用。

6.1配置网关服务多实例

修改网关服务的appliction.yml

spring:
  application:
    name: spring-cloud-gateway-server
  cloud:
    gateway:

      # 配置路由,可以配置多个
      routes:
        - id: order-microservice-router  # id 自定义路由的id
          # uri就是目标服务地址,这里使用服务名的方式,gateway会帮我们去注册中心中获取服务列表
          uri: lb://spring-cloud-order-service-provider
          predicates:   # 断言,也就是路由条件 ,这里使用了path作为路由条件
            - Path=/order/**
        - id: user-microservice-router
          uri: lb://spring-cloud-user-service-consumer
          predicates:
            - Path=/user/**
#eureka 配置
eureka:
  client:
    service-url:
      # eureka server url
      defaultZone: http://EurekaServerA:9090/eureka,http://EurekaServerB:9091/eureka
    register-with-eureka: true
    fetch-registry: true
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

---
spring:
     profiles: g1
server:
  port: 8020
---
spring:
     profiles: g2
server:
  port: 8021

这样启动的时候可以配置一下idea
在这里插入图片描述
这样我们就有了8020 与8021 两个网关实例

6.2 nginx配置

如果没有nginx ,可以去 http://nginx.org/en/download.html 下载一个,这个地方选择自己系统下载就好了。
然后就是nginx安装,window 解压就能用,linux系统稍微麻烦些也可以使用yum安装,mac系统可以直接使用brew 安装 brew install nginx
接着就是nginx的配置文件了,修改nginx.conf

#user  nobody;
worker_processes  1;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    #access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    keepalive_timeout  65;
    #gzip  on;
    upstream gateway {
  		server 127.0.0.1:8020;
  		server 127.0.0.1:8021;
	}
    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://gateway;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

这里主要是

 upstream gateway {
  		server 127.0.0.1:8020;
  		server 127.0.0.1:8021;
 }
location / {
       proxy_pass http://gateway;
}

接着启动一下nginx

6.3 测试访问

因为咱们nginx是80端口,所以直接在浏览器访问:http://127.0.0.1/order/data/getAppName/100
在这里插入图片描述
接着咱们模拟一个网关服务挂了(手动关闭一个8021)
然后访问,还是能访问到
在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐