SpringCloud—05—中级之服务网关

提前预知

学习一种技术最好的方式是:视屏+官方文档!!!!!

官网文档地址:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/

注意:这个是H版的SR1对应的文档,如果想看其他版本的直接更改链接中的版本号即可,例如查看Hoxton.SR2版本,如下:

https://cloud.spring.io/spring-cloud-static/Hoxton.SR2/reference/htmlsingle/

代码地址:https://gitee.com/aismall/spring-cloud

本次笔记对应的课程为尚硅谷的Springcloud教程,课程地址:springcloud课程地址

课程包含:springcloud+springcloud alibaba

课程分为四个等级(不一定要一次性学完,你懂得!!!):

  • 零:1~4
  • 初:5~9
  • 中:10~16
  • 高:17~21

课程大纲:
在这里插入图片描述
一点一点的学习下面这些组件!!!!!
在这里插入图片描述

11、Zuul路由网关

由于Zuul这个网关已经被遗弃了,对比之下还是学习常用的网关技术比较好,也就是新一代的网关GateWay!!!

技术是学不完的,学习实用的即可!!!!

对Zuul网关感兴趣的可以移步至官网学习:https://github.com/Netflix/zuul/wiki

12、Gateway新一代网关

12.1、GateWay是什么

官方文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-gateway

概述

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway—句话:gateway是原zuul1.x版的替代

还记得下面这张图吗
在这里插入图片描述
官网介绍如下:

  • Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。

  • Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断限流重试等。

  • SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式。

  • SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

  • Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全监控/指标,和限流

作用

- 方向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
- ...
网关在微服务架构中的位置

在这里插入图片描述

12.2、GateWay非阻塞异步模型

有Zuul了怎么又出来Gateway?我们为什么选择Gateway?

  • 原因1:Netflix不太靠谱,Zuul2.0一直跳票,迟迟不发布。

  • 原因2:Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖,而且很多功能Zuul都没有用起来,也非常的简单便捷。

  • 原因3:Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期,不知前景如何?

  • 多方面综合考虑Gateway是很理想的网关选择。

SpringCloud Gateway具有如下特性:

  • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;

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

  • 可以对路由指定Predicate (断言)Filter(过滤器)

  • 集成Hystrix的断路器功能(Hystrix的部分功能);

  • 集成Spring Cloud 服务发现功能(Eureka的功能);

  • 易于编写的Predicate (断言)和Filter (过滤器);

  • 请求限流功能;

  • 支持路径重写。

SpringCloud Gateway与Zuul的区别

  • SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul,Zuul 1.x是一个基于阻塞I/O的API Gateway,Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket),

  • Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。

  • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。

  • Zuul 2.x的性能较Zuul 1.x有较大提升,在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。

  • Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API

  • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

Zuul1.x模型

  • Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。

  • Servlet的生命周期?servlet由servlet container进行生命周期管理,container启动时构造servlet对象并调用servlet init()进行初始化,container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service),container关闭时调用servlet destory()销毁servlet。
    在这里插入图片描述

上述模式的缺点:

  • Servlet是一个简单的网络IO模型,当请求进入Servlet container时,Servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。

  • 但是一旦高并发(如用Jmeter压测),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。

  • 在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。

  • 所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即Spring实现了处理所有request请求的一个servlet (DispatcherServlet)并由该servlet阻塞式处理处理。所以SpringCloud Zuul无法摆脱servlet模型的弊端。

Gateway模型

  • WebFlux是什么?官方文档

  • 再看看下面这张图
    在这里插入图片描述

  • 传统的Web框架,比如说: Struts2,SpringMVC等都是基于Servlet APl与Servlet容器基础之上运行的。

  • 但是在Servlet3.1之后有了异步非阻塞的支持,而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的,相对于传统的web框架来说,它可以运行在诸如NettyUndertow及支持Servlet3.1的容器上。

  • 非阻塞式+函数式编程(Spring 5必须让你使用Java 8及其以上版本)。

  • Spring WebFlux是Spring 5.0 引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

  • 官网一句话如下

12.3、Gateway工作流程:三大核心概念

三大核心概念

  • Route(路由) :路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由;
  • Predicate(断言) : 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
  • Filter(过滤) : 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

在这里插入图片描述

  • web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。

  • predicate就是我们的匹配条件;而fliter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程
在这里插入图片描述

  • 客户端向Spring Cloud Gateway发出请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。

  • Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

  • 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。

  • Filter为pre类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

  • Filter为post类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑:路由转发 + 执行过滤器链。

12.4、Gateway搭建

1、新建Module: cloud-gateway-gateway9527

2、POM

<dependencies>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--eureka-client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.aismall</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!--一般基础配置类-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3、YML

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7003.com:7003/eureka

4、业务类:无,是不是很NICE!!!!!

5、主启动类

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527{
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class, args);
    }
}

6、9527网关如何做路由映射?

  • 我们目前不想暴露8001端口,希望在8001外面套一层9527

  • 我们需要配置路由转发,这个要结合cloud-provider-payment8001中具体的访问地址来做,看看controller的访问地址,下面给出cloud-provider-payment8001服务中的controller的代码:

    @RestController
    @Slf4j
    public class PaymentController{
        @Resource
        private PaymentService paymentService;
    
        @Resource
        private DiscoveryClient discoveryClient;
    
        @Value("${server.port}")
        private String serverPort;//添加serverPort
    
        @PostMapping(value = "/payment/create")
        public CommonResult create(@RequestBody Payment payment) {
            int result = paymentService.create(payment);
            log.info("*****插入结果:"+result);
    
            if(result > 0) {
                return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort, result);
            }else{
                return new CommonResult(444,"插入数据库失败,serverPort: "+serverPort,null);
            }
        }
    
        @GetMapping(value = "/payment/get/{id}")
        public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
            Payment payment = paymentService.getPaymentById(id);
    
            if(payment != null) {
                return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
            }else{
                return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
            }
        }
    
        @GetMapping("/payment/lb")
        public String getPaymentLB(){
            return serverPort;
        }
        
        @GetMapping("/payment/discovery")
        public DiscoveryClient discovery(){
            // 得到所有服务名
            List<String> services = discoveryClient.getServices();
            services.forEach(ele->{
                log.info("***service***"+ele);
            });
            // 得到服务名对应的信息
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
            instances.forEach(ele->{
                log.info(ele.getServiceId()+"\t"+ele.getHost()+"\t"+ele.getPort()+"\t"+ele.getUri());
            });
    
            return discoveryClient;
        }
        
        // 新增一个方法
        @GetMapping(value = "/payment/feign/timeout")
        public String paymentFeignTimeout(){
            // 业务逻辑处理正确,但是需要耗费3秒钟
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return serverPort;
        }
    }
    

7、修改9527的YML:新增网关配置

配置两个路由转发:
- @GetMapping(value = "/payment/get/{id}")
	- http://localhost:8001/payment/get/{id}
- @GetMapping("/payment/lb")
	- http://localhost:8001/payment/lb
server:
  port: 9527

spring:
  application:
    name: cloud-gateway
#############################新增网关配置###########################
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
####################################################################

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7003.com:7003/eureka

8、测试

- 启动7003

- 启动8001:cloud-provider-payment8001

- 启动9527网关
  • 访问说明
- 添加网关前:http://localhost:8001/payment/get/1
- 添加网关后:http://localhost:9527/payment/get/1
- 两者访问成功,返回相同结果

12.5、Gateway配置路由的两种方式

方式一:在网关服务的配置文件yml中配置,见上一章节

方式二:代码中注入RouteLocator的Bean

如何实现那?

  • 业务需求:通过9527网关访问到国内的百度新闻网址

    百度国内新闻网址:http://news.baidu.com/guonei
    
  • 修改cloud-gateway-gateway9527添加一个配置类如下:

    @Configuration
    public class GateWayConfig{
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
            routes.route("path_route_aismall",
                    r -> r.path("/guonei")
                            .uri("http://news.baidu.com/guonei")).build();
    
            return routes.build();
        }
    }
    

测试:启动cloud-gateway-gateway9527微服务

  • 浏览器输入:http://localhost:9527/guonei,返回http://news.baidu.com/guonei相同的页面。

12.6、GateWay配置动态路由

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能(不写死一个地址)。
在这里插入图片描述

启动

- eureka7003
- payment8001/8002

修改9527的POM文件:添加过就不用再加了

<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改9527的YML文件:

  • 需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。

  • lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri。

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
    #############################新增网关配置###########################
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          routes:
            - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service      #匹配后提供服务的路由地址(不能写死,写注册的服务名)
              predicates:
                - Path=/payment/get/**             # 断言,路径相匹配的进行路由
    
            - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service      #匹配后提供服务的路由地址(不能写死,写注册的服务名)
              predicates:
                - Path=/payment/lb/**              # 断言,路径相匹配的进行路由
    ####################################################################
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://eureka7001.com:7001/eureka
    

测试

  • 浏览器输入:http://localhost:9527/payment/lb

  • 结果:不停刷新页面,8001/8002两个端口切换。

12.7、GateWay常用的Predicate

官方文档

Route Predicate Factories这个是什么?
在这里插入图片描述

  • Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

  • Spring Cloud Gateway包括许多内置的Route Predicate工厂,所有这些Predicate都与HTTP请求的不同属性匹配,多个RoutePredicate工厂可以进行组合。

  • Spring Cloud Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories,所有这些谓词都匹配HTTP请求的不同属性,多种谓词工厂可以组合,并通过逻辑and。、

常用的Route Predicate Factory

- The After Route Predicate Factory
- The Before Route Predicate Factory
- The Between Route Predicate Factory
- The Cookie Route Predicate Factory
- The Header Route Predicate Factory
- The Host Route Predicate Factory
- The Method Route Predicate Factory
- The Path Route Predicate Factory
- The Query Route Predicate Factory
- The RemoteAddr Route Predicate Factory
- The weight Route Predicate Factory

讨论几个Route Predicate Factory的使用:在9527中配置

  • The After Route Predicate Factory
spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        # 这个时间后才能起效
        - After= 2021-08-21T16:19:55.980+08:00[Asia/Shanghai]
访问链接:http://localhost:9527/
  • 可以通过下述方法获得上述格式的时间戳字符串
public class TimeStamp{
    public static void main(String[] args){
        ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
        System.out.println(zbj);
       //2021-02-22T15:51:37.485+08:00[Asia/Shanghai]
    }
}
  • The Between Route Predicate Factory
spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://example.org
        # 两个时间点之间
        predicates:
        - Between=2021-08-21T16:19:55.980+08:00[Asia/Shanghai], 2022-08-21T16:19:55.980+08:00[Asia/Shanghai]
访问链接:http://localhost:9527/
  • The Cookie Route Predicate Factory
spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p
The cookie route predicate factory takes two parameters, the cookie name and a regular expression.

This predicate matches cookies that have the given name and whose values match the regular expression.
测试:
	该命令相当于发get请求,且没带cookie
	- curl http://localhost:9527/

	带cookie的
	- curl http://localhost:9527/ --cookie "chocolate=chip"
  • The Header Route Predicate Factory
spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+
The header route predicate factory takes two parameters, the header name and a regular expression.

This predicate matches with a header that has the given name whose value matches the regular expression.
测试
	带指定请求头的参数的CURL命令
	- curl http://localhost:9527/ -H "X-Request-Id:123"
  • 其它的,举一反三。

小结

  • 说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

12.8、GateWay的Filter

官方文档
在这里插入图片描述

  • 路由过滤器:可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
  • Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

Spring Cloud Gateway的Filter:

  • 生命周期:
pre
post
  • 种类(具体看官方文档):
- GatewayFilter -31- GlobalFilter -10- 上面的过滤器可以参考官方文档自己配一配,感受一下!!!!
  • 两个主要接口介绍:
- GlobalFilter

- Ordered
  • 能干什么:
- 全局日志记录
- 统一网关鉴权
- ...

代码案例:GateWay9527项目添加MyLogGateWayFilter类:

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        log.info("***********come in MyLogGateWayFilter:  "+new Date());

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");

        if(uname == null)
        {
            log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

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

测试:

启动
- EurekaMain7003
- PaymentMain8001
- GateWayMain9527
- PaymentMain8002

浏览器输入:

- http://localhost:9527/payment/lb - 反回异常

- http://localhost:9527/payment/lb?uname=abc - 正常访问
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彤彤的小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值