SpringCloud 之 网关 Gateway

128 阅读7分钟

统一网关Gateway

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

简介

为什么需要网关呢?

我们之前的项目完成了多个微服务的搭建,将它们都注册到了Nacos配置中心,并且在nacos的配置中心做了统一的配置管理,了解了bootstrap的配置文件等,并且我们还对各个微服务之间引入了feign,是他们的调用更加清晰明确。

但是,现在有个问题,我们的微服务是不是都暴露给任何人了呢?我们的请求数量要是太多了,是不是就会摧毁我们的微服务呢?

因此,网关就出现了

我们来看看网关的功能吧.

1.身份的验证和权限校验

这能限制一些人的访问,使得微服务并不是暴露给任何人

2.服务路由,负载均衡

使得当外人访问这个网关的时候,比如说需要访问orderservice,网关就会路由到这个服务,在多个orderservice的集群中,可以选择相对空闲的那个,实现负载均衡

3.请求限流

举个例子,如果现在有2000个请求涌进来,但是整个微服务只能承受500个,这时候先流的作用就是更好的保护整个微服务

下面我们看一张图,来了解整体的架构(draw.io画的图)

image-20221010121752017.png

总结:网关就是保护膜

技术实现

在SpringCloud的网关实现中包括这两种:

1.gateway

2.zuul

这里我们用非阻塞式的SpringCloudGateway

搭建网关Gateway

1.创建一个gateway的模块,引入nacos服务注册的依赖和gateway的依赖

<!--        nacos服务发现依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
<!--        gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

2.之后写个启动类

3.注册gateway服务到nacos中,配置路由

server:
  port: 10010
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service  # 路由标识
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断是否符合/user开头,如果是,则符合
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**

注意,这里的网关本质上也是个微服务,它负责转发请求。

这里的断言限定了:如果访问localhost:10010/user他就会帮忙转发到http://userservice/user上,也就是http://localhost:8081/user

lb则代表loadbalance,它会自动帮你负载均衡

image-20221010125417323.png

路由断言工厂Route Predicate Factory

我们在配置文件里面写的断言规则只是字符串,这些字符串会被Predicate Factoy读取并且处理,转变为路由判断的条件

例如:Path=/user/**是按照路径匹配的。它是由断言工厂的一个类来处理的

像这样的断言工厂,SpringCloudGateway还有十几个

image-20221010130024049.png

这个网站上有特别详细的案例描述,可以根据自己的选择去限制转发

image-20221010132653650.png

比如说我们试一下官网上的这样一个配置,意思是,只有17年前才能访问

image-20221010132733526.png

然后我们发现了404,配置生效!

路由统一过滤器 GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做相应的处理

简单来说,就是对请求和响应做些请求头添加啊,类似的操作。

image-20221010154242032.png

那么具体是做什么呢?

直接上官网链接

tmd,30多个过滤器,都在这个文章里面,你难道要一个一个去学吗,除非你是傻子,这些,用到的时候再看就可以,和上面的断言工厂一样,用到的时候再看即可

我们从官网上看一个吧

image-20221010154848772.png

比如第一个,那个filter就是加一个X-Request-Foo的请求头到下流的微服务上,key是X-Request-Foo,value是Bar

案例出发

给所有的userservice去添加一个请求头:Truth=wanghaotian is a handsome man

实现方式:

修改yml文件

server:
  port: 10010
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service  # 路由标识
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断是否符合/user开头,如果是,则符合
          filters:
            - AddRequestHeader=Truth, wanghaotian is a handsomeboy # 改动的地方
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**

然后修改一下controller

image-20221010155708493.png

用@RequestHeader接收请求头,然后打印,我们重启一下然后访问

image-20221010160138877.png

打印成功

全局过滤器 global Filter

全局过滤器的作用和GatewayFilter的作用相同,都是处理一切进入网关的请求

但是,你有没有想过,gatewayfilter的东西,是不是已经写死了?它没办法自定义!他处理的逻辑很固定

而GlobalFilter的逻辑需要自己写代码去实现

定义方式是实现GlobalFilter

下面我们来通过一个案例讲解:

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足以下条件:

1.参数中是否有authorization

2.authorization参数值是否为admin

同时满足就放行

我们定义一个AuthorizeFilter类实现GlobalFilter

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        //2.获取参数中的authorization参数
        String auth = params.getFirst("authorization");
        //3.判断是否等于admin
        if("admin".equals(auth)){
            //放行给下一个拦截器,相当于通过了拦截器
            return chain.filter(exchange);
        }
        //否就拦截,且设置未认证
        ServerHttpResponse response = exchange.getResponse();
        //设置响应状态码是401
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.setComplete();
    }
​
    @Override
    public int getOrder() {
        return -1;
    }
}

image-20221010171015323.png

注意,这里的order是拦截器的优先级,越小优先级越高,需要实现Ordered 接口,或者加上@Order(-1)的注解

写完了以后,我们先发一个没有authorization的请求,果然

image-20221010172303685.png

出现了我们设置的响应码401

当我们访问http://localhost:10010/user/1?authorization=admin的时候,成功访问了!

image-20221010172419634.png

过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由过滤器(Path:/user/**),DefaultFilter,GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

也许有人会有疑问,这三个过滤器,类型都不一样,还能放到一个集合当中?并且还排序?

image-20221011111836830.png

我们不妨先点进去看看,

image-20221011112055177.png

原来我们发现,路由过滤器和default过滤器是同一类,都是gatewayfilter

那GlobalFilter呢?

image-20221011112240275.png

原来这里使用了适配器的设计模式,所有的GlobalFilter都可以被适配成gatewayfilter

因此,网关里面所有的类型都是gatewayfilter。

还有一个问题,我们在globalfilter里面制定了order值用来排序,可是路由过滤器和default过滤器都没有指定,那么他们又是怎么进行排序的呢?

原来路由过滤器和default过滤器的order值由spring指定,默认按照声明顺序由1递增

当过滤器的order值都一样的时候,会按照defaultFilter>路由过滤器>GlobalFilter的顺序去执行

网关的跨域问题处理

什么是跨域?

其实很简单,就是前端请求的网址和前端自己的网址的域名不一样,或者端口不一样

但是我们之前8080口的user服务为什么可以调用8081口的order服务呢?

其实我们又可以对跨域做一个定义的再完善:即浏览器禁止请求的发起者与服务端跨域ajax请求,请求会被浏览器拦截。

我们前端一直用的anxios,底层也是ajax噢

解决方案:CORS(浏览器问服务器给不给跨域)

网关处理同样采取CORS方案,并且只需要简单配置即可

      globalcors:
        add-to-simple-url-handler-mapping: true # 解决预检请求被拦截
        cors-configurations:
          '[/**]':  # 对于一切请求拦截处理
            allowedOrigins: # 允许访问的源
              - "http://localhost:8090"
              - "https://www.leyou.com"
            allowedMethods:# 允许访问的类型
              - "GET"
              - "POST"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许一切请求头
            allowCredentials: true # 允许携带cookie
            maxAge: 3600 # 每3600s进行一次拦截