zoukankan      html  css  js  c++  java
  • spingcloud(8) gateway

    一。简介

    springcloud是第二代网关,取代zuul网关。具有强大的智能路由,过滤器功能。常见的功能有路由转发、权限校验、限流控制等作用

    Spring Cloud Gateway 具有如下特性:

    • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
    • 动态路由:能够匹配任何请求属性;
    • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
    • 集成Hystrix的断路器功能;
    • 集成 Spring Cloud 服务发现功能;
    • 易于编写的 Predicate(断言)和 Filter(过滤器);
    • 请求限流功能;
    • 支持路径重写。

    相关概念

    • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
    • Predicate(断言):匹配请求中的信息,与断言匹配成功则进行路由。
    • Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。

    二。代码

    pom.xml添加依赖

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

    有两种配置路由的方式:一种是在yml文件来配置,另一种是通过java bean来配置。下面例子大部分用yml

    java bean配置

    SpringBootApplication
    @RestController
    public class SpringcloudGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudGatewayApplication.class, args);
        }
    
        /**
         * java配置路由会覆盖application.yml中的配置
         * @param builder
         * @return
         */
       @Bean
        public RouteLocator myRoutes(RouteLocatorBuilder builder){
            String httpUri = "http://httpbin.org:80";
            return builder.routes()  //route来做路由
                    .route("rout1",p -> p
                      .path("/get") // 对/get请求做处理
                      .filters(f->f.addRequestHeader("hello", "yy")) //过滤器增加header
                      .uri(httpUri)) //转发到这个请求上
                    .build();
        }
    }

    可以看到请求/get 的header添加了参数hello=yy

    1.predicate实战

    After Route Predicate Factory

    可配置在指定时间后的请求,都交给router处理。否则不通过路由

    server:
      port: 8081
    spring:
      profiles:
        active: add_request_header_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: after_route
              uri: http://httpbin.org:80/get #前面的路径替换为该值即http://xxx:8081这段
              filters: #增加请求header的过滤器
                - AddRequestHeader=X-Request-Foo, Bar
              predicates:
                - After=2017-01-20T17:42:47.789-07:00[America/Denver]
      profiles: add_request_header_route

    启动工程,在浏览器上访问http://localhost:8081/,会显示http://httpbin.org:80/get返回的结果,此时gateway路由到了配置的uri。

    Header Route Predicate Factory

    Header Route Predicate Factory需要两个参数,header名称和值,当断言匹配了请求中的header名称和值后,断言通过,就能进入路由规则

    spring:
      profiles:
        active: header_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: header_route
              uri: https://www.baidu.com
              predicates:
                - Header=X-Request-Id, d+ #当请求的Header中有X-Request-Id的header名,且header值为数字时
      profiles: header_route

    测试:

    curl -H "X-Request-Id:1" localhost:8081

    返回页面请求就通过了,404则没有通过

    spring:
      profiles:
        active: cookie_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: cookie_route
              uri: http://httpbin.org:80/get
              predicates:
                - Cookie=name, yy
      profiles: cookie_route

    请求带有cookie名称为name,值为yy就能通过,被转发到http://httpbin.org:80/get

    测试

    curl -H "Cookie:name=forezp" localhost:8081

    Host Route Predicate Factory

    需要以一个参数即hostname,会匹配请求头中的host值,匹配则转发

    spring:
      profiles:
        active: host_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: host_route
              uri: http://httpbin.org:80/get
              predicates:
                - Host=**.yy.com
      profiles: host_route

    测试:curl -H "Host:www.fangzhipeng.com" localhost:8081

    Method Route Predicate Factory

    需要一个参数请求类型,GET

    spring:
      profiles:
        active: method_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: method_route
              uri: http://httpbin.org:80/get
              predicates:
                - Method=GET
      profiles: method_route

    测试:curl localhost:8081

    模拟POST请求:curl -XPOST localhost:8081

    Path Route Predicate Factory

    需要一个参数匹配路径

    spring:
      profiles:
        active: path_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: path_route
              uri: http://localhost:9600/
              predicates: 
                - Path=/hello
      profiles: path_route

    Query Route Predicate Factory

    需要两个参数,参数名和参数值,也可以只有一个参数即参数名

    spring:
      profiles:
        active: query_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: query_route
              uri: http://httpbin.org:80/get
              predicates:
                - Query=foo, ba.
      profiles: query_route

    测试:curl localhost:8081?foo=bar

    Predict作为断言,它决定了请求会被路由到哪个router 中。在断言之后,请求会被进入到filter过滤器的逻辑

    2.filter实战

     确定哪个路由执行后,在路由处理之前,先经过”pre“类型的过滤器,路由处理后,经过”post“类型的过滤器

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

    ”post“类型的过滤器可以做响应内容,响应头修改,日志输出,流量监控等。

    springcloud包含许多内置的GatewayFilter工厂,和predicate一样配置在application.yml,遵循了约定大于配置的原则,只需要配置filter工厂的名称,不需要配置全类名。

    比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名

    AddRequestHeader GatewayFilter Factory

    server:
      port: 8081
    spring:
      profiles:
        active: add_request_header_route #指定配置文件
    ---
    spring:
      cloud:
        gateway:
          routes:
          - id: add_request_header_route
            uri: http://httpbin.org:80/get
            filters:
            - AddRequestHeader=X-Request-Foo, Bar #AddRequestHeaderGatewayFilterFactory
            predicates:
            - After=2017-01-20T17:42:47.789-07:00[America/Denver] #AfterPredictFactory
      profiles: add_request_header_route

    模拟请求:curl localhost:8081

    最终显示了从 http://httpbin.org:80/get得到了请求,响应如下:

    {
      "args": {},
      "headers": {
        "Accept": "*/*",
        "Connection": "close",
        "Forwarded": "proto=http;host="localhost:8081";for="0:0:0:0:0:0:0:1:56248"",
        "Host": "httpbin.org",
        "User-Agent": "curl/7.58.0",
        "X-Forwarded-Host": "localhost:8081",
        "X-Request-Foo": "Bar"
      },
      "origin": "0:0:0:0:0:0:0:1, 210.22.21.66",
      "url": "http://localhost:8081/get"
    }

    从响应知道,请求头中加入了 "X-Request-Foo": "Bar"

    RewritePath GatewayFilter Factory

    重写路径的功能,类似nginx的功能

    spring:
      profiles:
        active: rewritepath_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: rewritepath_route
              uri: http://localhost:9600/
              predicates:
                - Path=/foo/**
              filters: #将/foo/(?.*)重写为{segment},然后转发
                - RewritePath=/foo/(?<segment>.*), /${segment}
      profiles: rewritepath_route

    比如在网页上请求localhost:8081/foo/forezp,此时会将请求转发到https://blog.csdn.net/forezp的页面

    Hystrix GatewayFilter

    允许将断路器功能添加到网关中,使服务免受级联故障??这个如何实现??, 提供服务降级处理

    pom.xml增加hystrix

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    • 增加服务降级处理类
    • @RestController
      public class FallbackController {
      
          @GetMapping("/fallback")
          public Object fallback() {
              Map<String,Object> result = new HashMap<>();
              result.put("data",null);
              result.put("message","Get request fallback!");
              result.put("code",500);
              return result;
          }
      }
    • 在application-filter.yml中添加相关配置,当路由出错时会转发到服务降级处理的控制器上:
    spring:
      cloud:
        gateway:
          routes:
            - id: hystrix_route
              uri: http://localhost:9600
              predicates:
                - Method=GET
              filters:
                - name: Hystrix
                  args:
                    name: fallbackcmd
                    fallbackUri: forward:/fallback

    访问http://localhost:8081/user转发到http://localhost:9600/user, 404错误转发到服务降级处理的控制器上,返回降级处理的信息

    现在yml中配置测试不行,在java bean中配置可以返回

    @Bean
        public RouteLocator myRoutes(RouteLocatorBuilder builder){
            String httpUri = "http://httpbin.org:80";
            return builder.routes()  //route来做断言
                    .route(p -> p
                      .host("*.hystrix.com")
                      .filters(f->f.hystrix(config -> config
                                .setName("mycmd")
                                .setFallbackUri("forward:/fallback")))
                      .uri(httpUri))
                    .build();
        }

    测试: curl -H "Host:www.hystrix.com" localhost:8081?token=1
    返回: {"code":500,"data":null,"message":"Get request fallback!"}

    自定义过滤器

     springcloud内置了19种过滤器工厂,也可以自动逸过滤器。在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口

    /**
     * 自定义实现过滤器
     */
    public class RequestTimeFilter implements GatewayFilter, Ordered {
        private static final Log log = LogFactory.getLog(RequestTimeFilter.class);
        private static final String REQUEST_TIME_BEGIN = "reqTimeBegin";
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 操作前执行
            exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    // 操作后执行
                    Mono.fromRunnable(()->{
                        Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                        if(startTime!=null){
                            log.info(exchange.getRequest().getURI().getRawPath() + ":" + (System.currentTimeMillis() - startTime) + "ms");
                        }
                    }));
        }
    
        /**
         * 给过滤器设定优先级别的,值越大则优先级越低
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }

    java中配置路由,使用自定义RequestTimeFilter

     @Bean
        public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
            // @formatter:off
            return builder.routes()
                    .route(r -> r.path("/customer/**")
                            .filters(f -> f.filter(new RequestTimeFilter())
                                    .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
                            .uri("http://httpbin.org:80/get")
                            .order(0)
                            .id("customer_filter_router")
                    )
                    .build();
            // @formatter:on
        }

    测试:curl localhost:8081/customer/123

    控制台输出请求时间的日志:

    2020-10-14 22:17:48.528  INFO 107912 --- [ctor-http-nio-8] com.yy.gateway.RequestTimeFilter         : /customer:28ms 

    自定义过滤器工厂

    这样就能在配置文件中配置,自定义过滤器了

    public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
        private static final Log log = LogFactory.getLog(RequestTimeGatewayFilterFactory.class);
        private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
        private static final String KEY = "withParams";
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList(KEY); //给config传参
        }
    
        public RequestTimeGatewayFilterFactory() {
            super(Config.class);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            return ((exchange, chain) -> {
    // 操作前执行
                exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
                return chain.filter(exchange).then(
                        // 操作后执行
                        Mono.fromRunnable(()->{
                            Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                            if(startTime!=null){
                                StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                        .append(":")
                                        .append(System.currentTimeMillis() - startTime)
                                        .append("ms");
                                if(config.isWithParams()){
                                    sb.append("params:").append(exchange.getRequest().getQueryParams());
                                }
                                System.out.println(sb.toString());
                                log.info(sb.toString());
                            }
                        }));
            });
        }
    
        /**
         * 接收filter参数
         */
        public static class Config{
            private boolean withParams;
    
            public boolean isWithParams() {
                return withParams;
            }
    
            public void setWithParams(boolean withParams) {
                this.withParams = withParams;
            }
        }
    }

    静态内部类类Config就是为了接收参数的,里边的变量名可以随意写,但是要重写List shortcutFieldOrder()这个方法‘

    注册RequestTimeGatewayFilterFactory  bean

        @Bean
        public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
            return new RequestTimeGatewayFilterFactory();
        }

    application.yml 增加配置

    spring:
      profiles:
        active: elapse_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: elapse_route
              uri: http://httpbin.org:80/get #前面的路径替换即http://xxx:8081这段
              filters: #增加自定义Factory的filter
                - RequestTime=true
              predicates:
                - Method=GET
      profiles: elapse_route

    测试:curl localhost:8081/customer/123

    控制台输出请求时间的日志

    global filter

    全局过滤器,不用在配置文件中配置,作用在所有路由上

    自定义全局路由,不包含token的请求不转发,需要实现GlobalFilter和Ordered接口

    /**
     * 全局过滤器,如果不包含token参数直接返回不路由
     */
    public class TokenGlobalFilter implements GlobalFilter, Ordered {
        private static final Log log = LogFactory.getLog(TokenGlobalFilter.class);
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            if(token == null || token.isEmpty()){
                log.info("token is empty");
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return -100;
        }
    }

    注入到容器

    /**
         * 将自定义全局过滤器注入到IOC容器
         * @return
         */
        @Bean
        public TokenGlobalFilter tokenGlobalFilter(){
            return new TokenGlobalFilter();
        }

    测试: curl localhost:8081

    控制台打印日志:2020-10-14 22:29:50.985  INFO 107912 --- [ctor-http-nio-2] com.yy.gateway.TokenGlobalFilter         : token is empty

    Spring Cloud Gateway限流

    RequestRateLimiterGatewayFilterFactory这个类,RequestRateLimiter 过滤器可以用于限流,使用RateLimiter实现来确定是否允许当前请求继续进行,如果请求太大默认会返回HTTP 429-太多请求状态。使用Redis和lua脚本实现了令牌桶的方式

    pom.xml引入redis依赖

     <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifatId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>

    application.yml

    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: limit_route
              uri: http://localhost:9600/ #前面的路径替换即http://xxx:8081这段
              predicates:
                - Method=GET
              filters:
                - name: RequestRateLimiter
                  args:
                    key-resolver: "#{@hostAddrKeyResolver}" #用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象
                    redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率, 即平均访问速率每秒
                    redis-rate-limiter.burstCapacity: 2 #令牌桶总容量 即每秒最大访问速率
      profiles: limit_route
      redis:
        host: 192.168.31.211
        port: 6379

    KeyResolver需要实现resolve方法,比如根据Hostname进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。 

    /**
     * 根据Hostname进行限流,则需要用hostAddress去判断
     */
    public class HostAddrKeyResolver implements KeyResolver {
        Log log = LogFactory.getLog(HostAddrKeyResolver.class);
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    }
    @Bean
        public HostAddrKeyResolver hostAddrKeyResolver(){
            return new HostAddrKeyResolver();
        }

    多次访问http://localhost:8081,会返回429错误

    可以用其他维度限流,例如用户维度

        @Bean
        KeyResolver userKeyResolver(){
            return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
        }

    三。服务的注册与发现

     上面都是用硬编码方式进行路由转发,gateway可以配合注册中心进行路由转发

    pom.xml

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

    application.yml

    server:
      port: 8081
    
    spring:
      application:
        name: sc-gateway-service
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true
              lowerCaseServiceId: true
              
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka/

    spring.cloud.gateway.discovery.locator.enabled为true,表明gateway开启服务注册和发现的功能,并且自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。

    spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了),

    测试:http://localhost:8081/springcloud-eureka-client/hello 就被转发到http://localhost:9400/helllo

    如果不想写服务名来路由,自定义请求路劲

    server:
      port: 8081
    spring:
      application:
        name: sc_gateway_client
      cloud:
        gateway:
          discovery:
            locator:
              enabled: false #gateway开启服务注册和发现的功能,并且自动根据服务发现为每一个服务创建了一个router
              lower-case-service-id: true #请求路径上的服务名配置为小写
          routes:
            - id: springcloud-eureka-client
              uri: lb://SPRINGCLOUD-EUREKA-CLIENT #服务名
              predicates:
                - Path=/client/**
              filters:
                - StripPrefix=1 # 在转发之前将/client去掉
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka

    现在http://localhost:8081/client/hello 会被转发到http://localhost:9400/hello

     
  • 相关阅读:
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(2)
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(9)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(10)
    CKEditor在asp.net环境下的使用一例
    《Microsoft Sql server 2008 Internals》读书笔记第五章Table(7)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(11)
    千万数据的连续ID表,快速读取其中指定的某1000条数据?
    javascript中的float运算精度
    .Net与Java的互操作(.NET StockTrader微软官方示例应用程序)
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(6)
  • 原文地址:https://www.cnblogs.com/t96fxi/p/13817922.html
Copyright © 2011-2022 走看看