2021.11.23日记录——服务网关Gateway

SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
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链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
3、为什么选择Gateway?
Gateway的特性

基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;
动态路由:能够匹配任何请求属性;
可以对路由指定Predicate (断言)和Filter(过滤器);
集成Hystrix的断路器功能;
集成Spring Cloud 服务发现功能;
易于编写的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 .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紧密集成拥有更好的开发体验

http://www.bilibili.com/media...
http://www.bilibili.com/media...
http://www.bilibili.com/media...
http://www.bilibili.com/media...
http://www.bilibili.com/media...
http://www.thinksaas.cn/user/...
二、三大核心概念
1、三大概念

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

2、Gateway的工作流程

客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post")执行业务逻辑。
三、Gateway配置
1、环境
新建模块:cloud-gateway-gateway9527
pom:

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    cloud2021
    com.atguigu
    1.0.0-SNAPSHOT

4.0.0

com.atguigu.springcloud
cloud-gateway-gateway9527


    
    
        org.springframework.cloud
        spring-cloud-starter-gateway
    
    
    
        org.springframework.cloud
        spring-cloud-starter-netflix-eureka-client
    
    
    
        com.atguigu.springcloud
        cloud-api-commons
        ${project.version}
    
    
    
        org.springframework.boot
        spring-boot-devtools
        runtime
        true
    
    
        org.projectlombok
        lombok
        true
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    


复制代码
yaml:
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://eureka7001.com:7001/eureka

复制代码
主启动类:
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527
{

public static void main(String[] args) {
    SpringApplication.run(GateWayMain9527.class, args);
}

}
复制代码
业务类:无
2、gateway配置
为了保证可以路由跳转,加上如下代码:
server:
port: 9527

spring:
application:

name: cloud-gateway

#############################新增网关配置###########################
cloud:

gateway:
  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...就可以了。
3、编码方式配置
上面采用yaml方式配置,现在可以使用编码方式配置。
@Configuration
public class GateWayConfig
{

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
    RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

    routes.route("path_route_atguigu",
            r -> r.path("/guonei")
                    .uri("http://news.baidu.com/guonei")).build();

    return routes.build();
}

}
复制代码
直接访问http://localhost:9527/guonei
四、根据微服务名实现动态路由
这里要模拟使用gateway的方式实现动态路由,而不是ribbon了。
9527模块的yaml文件修改成这样:
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

复制代码
启动测试:localhost:9527/payment/lb,发现确实实现了轮询操作
五、Predicate的使用
9527启动后,发现后台出现了这些

进入gateway官网(上面有),发现一共有11种Predicate

Predicate是什么

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合。
Spring Cloud Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
After Route Predicate Factory的使用
按照官网的说法:

在yaml文件底下加上一行即可,但是要修改成中国时间:
测试,发现这样可以获取到上面格式的时间:
public class T2 {

public static void main(String[] args) {
    ZonedDateTime zbj = ZonedDateTime.now();
    System.out.println(zbj);
}

}

//输出
//2021-11-23T10:44:35.485+08:00[Asia/Shanghai]
复制代码
那么修改一下:
routes:

- id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
  #uri: http://localhost:8001          #匹配后提供服务的路由地址
  uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  predicates:
    - Path=/payment/lb/** 
    - After=2021-11-23T10:44:35.485+08:00[Asia/Shanghai]

复制代码
然后这样就代表上面这个路由只有在某某时间之后才能使用,现在把上面after的时间修改一下,再启动测试发现报错了,说明确实生效了,这种有点类似秒杀场景、或者项目定时上线都可以采用这种方式。

Cookie Route Predicate的使用
就是要求带不带cookie,或者带哪种cookie。
Cookie Route Predicate需要两个参数,一个是cookie name,一个是正则表达式。路由的规则会通过正则表达式与cookie进行匹配。

  • id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名

    uri: http://localhost:8001 #匹配后提供服务的路由地址

    uri: lb://cloud-payment-service #匹配后提供服务的路由地址
    predicates:

    • Path=/payment/lb/** # 断言,路径相匹配的进行路由
    • After=2021-11-23T10:44:35.485+08:00[Asia/Shanghai]
    • Cookie=username, James

    复制代码
    这里采用curl的方式发送请求:

其他的按照官网去写即可。
六、Filter的使用
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
过滤器一般只有两种:

GatewayFilter
GlobalFilter

自定义全局过滤器
过滤器太多了,要学也很麻烦,这里实现自定义过滤器,举个例子同时也更加通用,需要的话自己配即可:
创建个包filter,然后创建filter的类,需要实现两个接口:GlobalFilter、Ordered:
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{

@Override
public Mono 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;
}

}

你可能感兴趣的