四、SpringCloud服务网关

CY 2019年03月18日 520次浏览

对于网关Zuul来说,因为官方已经不再维护了,所以直接记录Gateway如何使用,这是Spring自己开发的网关。

Gateway

一些基本概念

Gateway用了很多的Spring5.0的新特性。

因为网关是挡在在前面的一面墙,所以他就能对请求做很多的事情,比如说鉴权,日志监控,这些在请求的时候和响应的时候能做的事情,最重要的就是鉴权了,有了鉴权就用不着每个服务里面去写鉴权功能了。

一般情况下,Nginx在最前面,然后通过Nginx访问到Gateway,然后通过Gateway访问每一个微服务。

Gateway的优点:

  • 动态路由:能够匹配任何请求属性

  • 可以对路由执行PredicateFilter

  • 集成Hystrix的断路器功能

  • 集成SpringCloud的服务发现功能

  • 易于编写的PredicateFilter

  • 请求限流功能

  • 支持路径重写

三个概念:

Route:路由时构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true,则匹配该路由。

Predicate:开发人员可以匹配HTTP请求中的所有内容,例如请求头或者请求参数,如果请求与断言相匹配则进行路由。

Filter:指的是Spring框架中的GatewayFilter实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

做一个网关路由

依然是同样的步骤:

新建一个Module,然后导入坐标:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

千万不能导入spring-boot-starter-web否则启动的时候会报错,因为Gateway默认使用的WebFlux

然后就是配置文件了。

server:
  port: 9527

启动类略,写一个最普通的启动类就可以了。

可以看到只需要一个端口就可以启动了,但是它并不能完成请求的路由。还需要其他配置

在其他服务中添加一些接口,比如说8001端口的服务中添加如下代码:

@GetMapping("router01/{id}")
public String router01(@PathVariable("id") Integer id) {
    return String.valueOf(id) + " router-01";
}

@GetMapping("router02/{id}")
public String router02(@PathVariable("id") Integer id) {
    return String.valueOf(id) + " router-02";
}

使用网关来路由到这两个接口上:

spring:
  cloud:
    gateway:
      routes:
        - id: router-01
          uri: http://localhost:8001
          predicates:
            - Path=/router01/**
        - id: router-02
          uri: http://localhost:8001
          predicates:
            - Path=/router02/**

id:和普通的id一样,不能重复,可以自己起名字

uri:映射到目标服务的uri

predicates:就是断言了,这里用到了一个Path断言

这个时候访问接口:http://localhost:9527/router01/154发现可以访问了,这就完成了路由功能

编码方式配置网关路由

使用编码的方式实现和上面配置文件中一样的功能:

@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator routes01(RouteLocatorBuilder builder) {
        return builder.routes().route("router-01",
                r -> r.path("/router01/**").uri("http://localhost:8001")
        ).build();
    }

    @Bean
    public RouteLocator routes02(RouteLocatorBuilder builder) {
        return builder.routes().route("router-02",
                r -> r.path("/router02/**").uri("http://localhost:8001")
        ).build();
    }
}

网关+注册中心

首先第一步导入坐标:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

配置文件稍作修改:

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: router-01
          uri: lb://cloud-provider-paymanet
          predicates:
            - Path=/router01/**
        - id: router-02
          uri: lb://cloud-provider-paymanet
          predicates:
            - Path=/router02/**
      discovery:
        locator:
          enabled: true
    consul:
      host: localhost
      port: 8500
      discovery:
        register: false

上面配置文件中加入了基本的注册中心的配置,并且启用了网关的注册中心功能,然后还将uri改成了lb:// + serviceId的形式。

然后启动类上面加上@EnableDiscoveryClient注解就可以了。

访问9527端口,可以发现网关对目标微服务也做了负载均衡,就意味着成功了。

Gateway默认的Predicate

Predicate

官方文档上有11中Predicate可以供使用,前面的例子中就使用了一种Path

  • After:在某个时间之后才可以访问,例如:2017-01-20T17:42:47.789-07:00[America/Denver],这里的时间是ZonedDateTime类生成的
  • Before:在某个时间之前才可以访问
  • Between:在某个时间中间才可以访问
  • Cookie:携带规定内容的Cookie才可以访问,例如:chocolate, ch.p
  • Header:携带规定内容头才能访问,例如:X-Request-Id, \d+
  • Host:当host为规定内容的时候才能访问,例如:**.somehost.org
  • Method:与指定方法匹配才能访问,例如:GET,POST
  • Path:这个前面用过,例如:/red/{segment},/blue/{segment},里面的segment是可以获取的,官网有说
  • Query:携带规定内容的查询字符串才能访问
  • RemoteAddr:规定的IP才能访问
  • Weight:给访问分配权重

Gateway的Filter

Filter的生命周期有prepost两种,过滤器的种类有GatewayFilterGlobalFilter两种。

GatewayFilter官方文档上列举了30种之多,GlobalFilter官方文档上列举了10种

重要的不是怎么使用官方的这些Filter,使用的时候查查文档就行了,重要的是学会怎么样自己定义过滤器,定义GlobalFilterGatewayFilter的过滤器,和Zuul的概念一样,过滤器也有优先级,所以实现一个全局的过滤器需要实现GlobalFilterOrdered接口。

这里需要有一些WebFlux的基础,其他文章中会记录。

实现代码如下:

@Component
@Slf4j
public class IpFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
        if (remoteAddress == null || "0:0:0:0:0:0:0:1".equals(remoteAddress.getHostName())) {
            log.info("不符合要求...");
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return response.setComplete();
        }
        log.info("经过了过滤器...");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

上面这个代码写到gateway项目的filter包中就可以了,这个代码的含义是不让本机访问。过滤器的写法不管多么麻烦,起点都是这里。