【SpringCloud组件学习笔记系列】(4)Gateway组件

SpringCloud组件学习笔记系列

【SpringCloud组件学习笔记系列】(1)Eureka组件
【SpringCloud组件学习笔记系列】(2)Hystrix组件
【SpringCloud组件学习笔记系列】(3)OpenFeign组件
【SpringCloud组件学习笔记系列】(4)Gateway组件
【SpringCloud组件学习笔记系列】(5)Config组件

完整代码已在Github开源:
https://github.com/Aliang99/SpringCloud_Bill

4、SpringCloud Netflix Gateway的使用以及讲解

4.1、简介

  • Spring Cloud Gateway是Spring官网基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关 服务。

  • Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。

  • Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式

  • Spring Cloud Gateway是替代Netflflix Zuul的一套解决方案。

  • Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发到对应的微服务。

  • Spring Cloud Gateway是加载整个微服务最前沿的防火墙和代理器,隐藏微服务节点Ip端口信息,从而加强安全防护。

  • Spring Cloud Gateway本身也是一个微服务,所以需要将其组测到Eureka服务注册中心去。

SpringCloud Gateway的核心功能是:过滤和路由(转发请求)

不管是来自客户端的请求还是服务内部调用,一切对服务的请求,都可以经过网关,然后网关来实现鉴权、动态路由等操作。Gateway是服务的统一入口。

4.2、核心概念

  • **路由(route)**路由信息的组成:由一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为真,说明请求URL和配置路由匹配。

  • **断言(Predicate)**Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的 ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于HTTP Request中的任何信息比如请求头和参数。 比如:必须携带某个参数才能访问服务A。

  • **过滤器(Filter)**一个标准的Spring WebFilter。 Spring Cloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter(局部)和Global Filter(全局)。过滤器Filter将会对请求和响应进行修改处理。比如:对所有的请求添加请求头,对所有的请求路径做截取操作。

4.3、Gateway的使用

4.3.1、创建工程模块

引入依赖

<dependencies>
  <!-- Gateway依赖包-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
   <!--Eureka依赖包-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
4.3.2、编写启动类
@SpringBootApplication
@EnableDiscoveryClient // 开启eureka的服务发现
public class SpringCloud_Gateway_10010_Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloud_Gateway_10010_Application.class, args);
    }
}
4.3.3、编写配置文件
server:
  port: 10010 # 设置端口
spring:
  application:
    name: api-gateway # 设置服务名称
  cloud:
    gateway:
      routes:
        - id: user-service-gateway  # 路由服务的id,随便起
          uri: http://127.0.0.1:8001 # 将符合断言的请求,转发到这个地址
          predicates: # 指定断言的规则,即配置映射路径
            - Path=/user/**  # 断言的规则,
            # 如果请求地址为 http://127.0.0.1:10010/user/queryById/1 ,则符合断言规则,这个请求就会被转发到uri指定的服务地址 http://127.0.0.1:8001/user/queryById/1
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka #指定eureka地址
  instance:
    prefer-ip-address: true  # 优先使用ip地址
4.3.4、启动服务
4.3.5、测试

输入http://127.0.0.1:10010/user/queryById/1,输出结果

image-20220121174413779

查看控制台打印情况,看Gateway组件是否将请求转发到了8001服务

image-20220121174546836

8001控制台显示,方法被执行了

4.3.6、面向服务的路由

在上面的案例中,路由规则对应的服务地址是写死的,即URI的值,如果在集群模式下,这样的做法显然是不合理的

应该是根据服务名称,去Eureka注册中心查找该名称对应的所有实例列表,然后进行动态路由

所以Gateway的处理是,将URI的值,指定为服务名称,并在服务名称前用lb代替http

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service-gateway
          # uri: http://127.0.0.1:8001 # 将符合断言的请求,转发到这个地址
          uri: lb://User-Service # 将上面的地址,更换成服务名称
          predicates: 
            - Path=/user/**

替代httplb表示LoadBalancerClient的意思,gateway会将后面的User-Service通过Eureka解析为实际的主机和端口,并进行Ribbon负载均衡处理。

配置好之后,重启服务,再次测试刚才的请求地址,http://127.0.0.1:10010/user/queryById/1,控制台输出信息

image-20220121181925761

Gateway服务的打印信息中显示采取了Ribbon,表示配置成功了。

4.4、路由前缀

客户端的请求地址与微服务的服务地址如果不一致的时候,可以通过配置路径过滤器实现路径前缀的添加和去除。

添加前缀举例:

http://127.0.0.1:10010/8 - > http://127.0.0.1:8001/user/queryById/8 添加路径前缀为/user/queryById

去除前缀举例:

http://127.0.0.1:10010/api/user/queryById/8 - > http://127.0.0.1:8001/user/queryById/8 去除的路径前缀为/api

这样的处理,也可以有效提升请求的安全性。

4.4.1、添加前缀的配置

改造原来的配置

spring:
  application:
    name: api-Gateway
  cloud:
    gateway:
      routes:
        - id: user-service-gateway
          uri: lb://User-Service
          predicates: 
            - Path=/**  # 将这里的user去掉,方便测试
          filters: 
            - PrefixPath=/user # 添加请求路径的前缀
4.4.2、测试添加前缀的效果

请求中并没有加user这个路径,仍然可以正常访问。因为Gateway在后台按照配置的前缀自动拼接了上去,并调用真实的请求地址获取结果。

image-20220121183606436

image-20220121183734211

4.4.3、实现去除前缀的配置
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service-gateway
          uri: lb://User-Service
          predicates: 
            - Path=/** # 此处指定拦截路径
          filters: 
            - StripPrefix=1 # 此处指定去除前缀的段数

StripPrefix这个取值为1 表示请求地址为http://127.0.0.1:10010/user/queryById/1时,该请求路径去掉/user这一段,如果为2,则去掉/user/queryById这两段,以此类推

4.4.4、测试去除前缀的效果

image-20220121203145540

可以看到,正常情况下,请求的真实地址应该是:http://127.0.0.1:10010/user/queryById/1,但输入的是http://127.0.0.1:10010/test/user/queryById/1依然可以正确访问到,就是因为去除前缀生效的缘故。

4.5、过滤器

4.5.1、过滤器的介绍

Gateway作为网关组件其中一个重要功能就是实现请求的鉴权,这个功能是需要通过Gateway的过滤器来实现,过滤器也可以实现路由前缀,是Gateway的核心功能。

Gateway自带几十种过滤器,常见的有:

  • AddRequestHeader : 对匹配上的请求加上Header
  • AddRequestParameters :对匹配上的请求路由添加参数
  • AddResponseHeader :对从网关返回的响应添加Header
  • StripPrefifix:对匹配上的请求路径去除前缀

进入GatewayFilterFactory接口,可以看到其它暂未介绍的过滤器

image-20220121205212019

这些过滤器在配置文件中的使用是,将去掉后面的GatewayFilterFactory之后的类名称作为参数名称。

具体可以参考PrefixPath以及StripPrefix这两个参数在配置文件以及全类名上的区别。

4.5.2、过滤器类型

过滤器分为局部过滤器以及全局过滤器

局部过滤器:

通过spring.cloud.gateway.routes.filters配置在具体路由下,只作用在当前路由上

通过spring.cloud.gateway.default-filters配置,会对所有路由生效,既是局部过滤器,也是全局过滤器。但这些过滤器的实现都是要实现GatewayFilterFactory接口。

全局过滤器:

不需要在配置文件中配置,作用在所有的路由上,实现GlobalFilter接口即可。后面会详细讲解实现方式。

4.5.3、过滤器的生命周期

Spring Cloud Gateway 的 Filter 的生命周期也类似Spring MVC的拦截器有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用

image-20220121212900233

这里的 pre 和 post 可以通过过滤器的 GatewayFilterChain 执行fifilter方法前后来实现

常见的使用场景:

请求鉴权:一般 GatewayFilterChain 执行fifilter方法前,如果发现没有访问权限,直接就返回空。

异常处理:一般 GatewayFilterChain执行fifilter方法后,记录异常并返回。

服务调用时长统计: GatewayFilterChain 执行fifilter方法前后根据时间统计。

4.5.4、默认过滤器

默认过滤器也是一种局部过滤器,只不过所有路由都会起作用。

配置默认过滤器

spring:
  application:
    name: api-gateway # 设置服务名称
  cloud:
    gateway:
      routes:
        - id: user-service-gateway  # 路由服务的id,随便起
          # uri: http://127.0.0.1:8001 # 将符合断言的请求,转发到这个地址
          uri: lb://User-Service # 将符合断言的请求,转发到这个地址
          predicates: # 指定断言的规则,即配置映射路径
            - Path=/**  # 断言的规则,
            # 如果请求地址为 http://127.0.0.1:10010/user/queryById/1 ,则符合断言规则,这个请求就会被转发到uri指定的服务地址 http://127.0.0.1:8001/user/queryById/1
          filters:
            - StripPrefix=1 # 这个取值为1 表示请求地址为http://127.0.0.1:10010/user/queryById/1是,去掉/user这一段,如果为2,则去掉/user/queryById这两段,以此类推
      default-filters:  # 默认过滤器,对所有路由都有效
        - AddResponseHeader=zidingyijian,zidingyizhi # 配置添加响应头内容,逗号前是键,逗号后是值

default-filters 用于指定过滤器,AddResponseHeader是添加响应头信息。

测试效果

去除了前缀,添加了响应头。

image-20220121211948756

4.5.5、自定义局部过滤器

实现一个自定义局部过滤器,根据案例学习

需求:

在过滤器(MyParamGatewayFilterFactory) 中将 http://127.0.0.1:10010/api/user/queryById/1?name=zhangsan&name=lisi中的参数name的值,获取到并输出到控制台;

**注意:**参数名是可变的,也就是不一定每次都是name,这需要通过配置过滤器的时候配置参数名。

在application.yml中对某个路由配置过滤器,该过滤器可以在控制台输出配置文件中指定名称的请求参数的值。

4.5.5.1、修改配置文件
spring:
  application:
    name: api-gateway # 设置服务名称
  cloud:
    gateway:
      routes:
        - id: user-service-gateway  # 路由服务的id,随便起
          # uri: http://127.0.0.1:8001 # 将符合断言的请求,转发到这个地址
          uri: lb://User-Service # 将符合断言的请求,转发到这个地址
          predicates: # 指定断言的规则,即配置映射路径
            - Path=/**  # 断言的规则,
          filters:
            - MyParam=name  # MyParam是自定义的局部过滤器,全称是MyParamGatewayFilterFactory,它的作用是获取请求中指定参数名的值,并输出到控制台,这个指定参数名就是后面的name
            - StripPrefix=1 # 这个取值为1 表示请求地址为http://127.0.0.1:10010/user/queryById/1是,去掉/user这一段,如果为2,则去掉/user/queryById这两段,以此类推

filters下添加了MyParam=name, MyParamMyParamGatewayFilterFactory类的名字。

4.5.5.2、编写自定义过滤器类

这部分内容多,可以着重看注释。

/**
 * 1、自定义局部过滤器
 *      注意:这里按照需求来看,配置文件中只指定了单个值name,所以自定义的过滤器继承AbstractGatewayFilterFactory就可以了
 *                如果制定了键值对,那么需要继承AbstractNameValueGatewayFilterFactory,键值对并不适用当前的需求,所以不采用
 *      AbstractGatewayFilterFactory的泛型需要指定为过滤器内的Config类,可以参考其它过滤器的实现方式,模仿着编码
 */
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
  
  public static final String PARAM_NAME = "name";  //2、提取字符串,方便阅读管理
  
    public MyParamGatewayFilterFactory(){
       super(Config.class); // 3、调用父类构造方法,将内部类作为值传递,用于封装根据配置文件中配置的参数名获取到的参数值
    }
  
    // 4、对请求中获取的值的处理,多个值的情况
    public List<String> shortcutFieldOrder(){
        return Arrays.asList(PARAM_NAME); // 4.1、对应Config类中变量名,用于封装参数值,因为需求是单个值,就使用单个字符串就行,如果是键值对,
    }
  
    /**
     * 5、实现AbstractGatewayFilterFactory的抽象方法,编写获取到参数值之后的业务逻辑
     * 返回值类型GatewayFilter是一个函数式接口,它所接受的参数是ServerWebExchange和GatewayFilterChain,所以对于函数式接口这种返回值,需要将它实例化出来,这里采用lambda的方式处理
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply(MyParamGatewayFilterFactory.Config config) {

        return ((exchange, chain) -> {
            // 6、此处可以编写前置过滤器逻辑,此处可以去获取请求中name的值
            //6.1、获取请求对象
            ServerHttpRequest request = exchange.getRequest();
            //6.2、判断请求对象的参数key中,是否包含name
            if(request.getQueryParams().containsKey(config.name)){
                //6.3、如果包含,就获取这个参数名对应的值的集合,注意:这里类型为集合主要是因为可能有多个参数名同名
                List<String> names = request.getQueryParams().get(config.name);
                //6.4、遍历这个值的集合
                for (String name : names) {
                    System.out.println("局部过滤器获得参数:"+config.name+"="+name); //6.5、打印出键值对,键为字符串name,值为请求中携带的
                }
            }
            return chain.filter(exchange); //6.6、执行请求,将请求放行
            //6.7、此处可以编写后置过滤器逻辑,当前demo未做其它处理
        });
    }
  
      // 7、需要定义config类,用来为过滤器获取到请求中的参数时做进一步封装
      public static class Config{
        private String name; // 7.1、用来对应配置文件中MyParam过滤器指定的参数名
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}
4.5.5.3、重启服务测试

前端输入地址:

image-20220122012117727

后端打印结果:

image-20220122012138466

4.5.6、自定义全局过滤器

实现一个自定义全局过滤器,根据案例学习

需求:

编写全局过滤器,在过滤器中检查请求中是否携带Token请求头,如果Token请求头存在则放行,如果不存在或为空则设置返回状态码:未授权,不再执行下去

全局过滤器需要实现GlobalFilter

Ordered接口指明了过滤器的执行顺序(优先级),返回的值越小越优先执行。

4.5.6.1、编写自定义全局过滤器

全局过滤器是不需要在配置文件中配置的,只要交给spring管理即可。

/**
 * 自定义全局过滤器
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    /**
     * GlobalFilter 的实现方法
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取第一个名为token的请求头的值
        String token = exchange.getRequest().getHeaders().getFirst("token");
        // 使用工具类判断是否为空
        /**
         * 该方法方法说明:
         * StringUtils.isBlank(null)      = true
         *        StringUtils.isBlank("")        = true
         *        StringUtils.isBlank(" ")       = true
         *        StringUtils.isBlank("bob")     = false
         *        StringUtils.isBlank("  bob  ") = false
         */
        if(StringUtils.isBlank(token)){
            // 为空,设置响应码为UNAUTHORIZED,这是个枚举类,定义了http的各种响应码供使用,此处的UNAUTHORIZED的值是401
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); //为空,也就是请求头没有token,就结束并返回响应码
        }
        // 不为空,就继续执行
        return chain.filter(exchange);
    }
    /**
     * 接口Ordered的实现方法,返回的值越小,优先级越高
     */
    @Override
    public int getOrder() {
        return 1;
    }
}
4.5.6.2、使用浏览器测试

image-20220122155401260

因为需求中要判断的是请求头的内容,普通的浏览器无法自行添加请求头,所以需要借助工具ApiPost或PostMan去测试。

4.5.6.3、使用ApiPost工具测试:

无token的情况测试:

image-20220122155927636

有token的情况测试:

image-20220122160448300

4.6、负载均衡和熔断

Gatway默认整合了Hystrix以及Ribbon,但是所有的超时策略都是用的默认值。因此,在使用Gateway的负载均衡以及熔断机制时,有需要的地方要自行配置。

不过一般也不使用gateway做负载均衡以及熔断,了解即可。

hystrix:
  command:
    default:
      execution:
        isolation:
            timeoutInMilliseconds: 6000 #服务降级超时时间,默认1S
ribbon:
  ConnectTimeout: 1000 # 连接超时时长
  ReadTimeout: 2000 # 数据通信超时时长
  MaxAutoRetries: 0 # 当前服务器的重试次数
  MaxAutoRetriesNextServer: 0 # 重试多少次服务

4.7、跨域配置

一般网关都是所有微服务的统一入口,必然在被调用的时候会出现跨域问题。

跨域:在js请求访问中,如果访问的地址与当前服务器的域名、ip或者端口号不一致则称为跨域请求。若不解决则不能获取到对应地址的返回结果。

如:从在http://localhost:9090中的js访问 http://localhost:9000的数据,因为端口不同,所以也是跨域请求。

在访问Spring Cloud Gateway网关服务器的时候,出现跨域问题的话;可以在网关服务器中通过配置解决,允许哪 些服务是可以跨域请求的;具体配置如下:

server: 
	port: 10010 
spring: 
	application: 
		name: api-gateway 
	cloud: 
		gateway: 
			routes: # 路由id,可以随意写 
				- id: user-service-route 
				# 代理的服务地址 
				uri: lb://user-service 
				# 路由断言,可以配置映射路径 
				predicates: 
					- Path=/api/user/** 
				filters: 
				# 表示过滤1个路径,2表示两个路径,以此类推 
					- StripPrefix=1 # 自定义过滤器 
					- MyParam=name # 默认过滤器,对所有路由都生效 
			default-filters: 
				- AddResponseHeader=X-Response-Foo, Bar 
				- AddResponseHeader=abc-myname,lxs 
			globalcors: 
				corsConfigurations: 
					'[/**]': 
						allowedOrigins:  # 指定允许跨域请求的请求地址,可以单个可以多个,*表示全部 
							- "http://docs.spring.io" 
						allowedMethods: # 允许跨域请求的请求类型
							- GET

上述配置表示:可以允许来自 http://docs.spring.io 的get请求方式获取服务数据。

allowedOrigins 指定允许访问的服务器地址,如:http://localhost:10000 也是可以的。 ‘[/**]’ 表示对所有访问到网关服务器的请求地址。

**官网具体说明:**https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/multi/multi__cors_confifiguration.html

4.8、Gateway的高可用

启动多个Gateway服务,自动注册到Eureka,形成集群。如果是服务内部访问,访问Gateway,自动负载均衡,没问题。

但是,Gateway更多是外部访问,PC端、移动端等。它们无法通过Eureka进行负载均衡,那么该怎么办?

此时,可以使用其它的服务网关,来对Gateway进行代理。比如:Nginx

4.9、Gateway与feign的区别

Gateway 作为整个应用的流量入口,接收所有的请求,如PC、移动端等,并且将不同的请求转- 发至不同的处理微服务模块,其作用可视为nginx

大部分情况下用作权限鉴定、服务端流量控制

Feign 则是将当前微服务的部分服务接口暴露出来,并且主要用于各个微服务之间的服务调用

例如:订单服务访问用户服务,这种内部访问,可以使用feign将用户服务接口暴露出来,给订单服务使用。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!对于Spring Cloud Alibaba的学习笔记,我可以为你提供一些基本的信息和指导。在学习Spring Cloud Alibaba之前,你可能需要对Spring Cloud和Alibaba的相关技术有一定的了解。 Spring Cloud Alibaba是基于Spring Cloud开发的一套微服务框架,它融合了阿里巴巴的中间件技术栈,提供了一系列开箱即用的解决方案,包括服务注册与发现、分布式配置管理、消息驱动等功能。它旨在帮助开发者快速构建微服务架构。 以下是一些学习Spring Cloud Alibaba的步骤和资源推荐: 1. 了解Spring Cloud和Alibaba的基础知识:在开始学习Spring Cloud Alibaba之前,你需要对Spring Cloud和阿里巴巴的相关技术有一定的了解。你可以先学习Spring Cloud的核心概念和基本使用方式,再深入了解阿里巴巴的中间件技术栈。 2. 官方文档:Spring Cloud Alibaba官方文档是学习的重要参考资料,你可以从官方文档中了解框架的核心概念、使用方式以及各个组件的详细说明。 3. 示例代码:官方文档中通常会提供一些示例代码,你可以通过运行示例代码来实践学习,加深对框架的理解。 4. 开发实践:尝试在自己的项目中应用Spring Cloud Alibaba,可以从简单的项目开始,逐步扩展和深入应用框架的各个功能。 5. 社区资源:参与Spring Cloud Alibaba的相关社区活动,例如论坛、博客、技术分享等,与其他开发者交流和学习。 希望以上信息对你有所帮助!如果你有更具体的问题,欢迎继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值