springCloud Gateway讲解

经过2天的研究终于下手写下这篇文章哈哈

Gateway

是什么

Spring Cloud 全家桶中有个很重要的组件:网关。在 1.x 版本中使用的是 Zuul 网关,但是到了 2.x,由于Zuul的升级不断跳票,Spring Cloud 自己研发了一套网关组件:Spring Cloud Gateway。
Spring Cloud Gateway基于 Spring Boot 2.x,Spring WebFlux 和 Project Reactor 构建,使用了 Webflux 中的 reactor-netty 响应式编程组件,底层使用了 Netty 通讯框架。

做什么

反向代理 --后台接口统一暴露出口
鉴权 --统一验证token有效性,没效果直接返回
流量控制 --流量大的时候很容易导致服务器奔溃,所以需要控制流量
熔断 --当下游服务不可用时快速返回错误信息,而不是继续访问下游服务
日志监控 --谁访问了那个接口

架构图

springCloud Gateway讲解_第1张图片
image

怎么用

Gateway的三大概念

Route(路由):路由是构建网关的基本模块,它由 ID、目标 URI、一系列的断言和过滤器组成,如果断言为 true 则匹配该路由

Predicate(断言):参考的是 Java8 中的 java.util.function.Predicate。开发人员可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

Filter(过滤):指的是 Spring 框架中 GatewayFilter 的实例,使用过滤器,可以在请求被路由之前或之后对请求进行修改

以上三个概念的流程图

springCloud Gateway讲解_第2张图片
image

springCloud Gateway工作流程图

springCloud Gateway讲解_第3张图片
image

说明

客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关 Web 处理程序。该处理程序通过特定于请求的过滤器链来运行请求。 筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有 “前置“ 过滤器逻辑均被执行,然后发出代理请求,发出代理请求后,将运行“ 后置 ”过滤器逻辑。

总结

路由转发 + 执行过滤器链

路由配置(两种方式)

    1. 在application.yml中配置
server:
 port:  5510

spring:
  cloud:
    gateway:
      discovery:
        locator:
          #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
          lower-case-service-id: true
          enabled: true
      routes:
      - id: iot
        predicates: 
        - Path=/test/**
          Host="**.abc.org"
        uri: http://www.baidu.com
        filters:
        - StripPrefix= 1 #过滤几个前缀/为间隔
          AddResponseHeader="X-TestHeader",foobar 
      - id: test-servier
        predicates:
        - Path=/iot/config/**
        uri: lb://nacos-provider-example
        filters:
        - StripPrefix= 1 #过滤几个前缀/为间隔
    1. 通过编程方式
// static imports from GatewayFilters and RoutePredicates
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 
    return builder.routes() 
            .route(r -> r.host("**.abc.org").and().path("/test/**") 
                .filters(f ->{
                        f.addResponseHeader("X-TestHeader", "foobar"));
                        f.stripPrefix(1);
                        }
                .uri("http://httpbin.org:80") 
            )
            .route(r -> r.path("/iot/config/**") 
                .filters(f ->
                        f.stripPrefix(1))
                .uri("lb://nacos-provider-example #" ) 
            )
            .build();
}
  • 亲测以上两种配置方式完全可以完成路由的转发,当然配置文件的方式配置出来的东西让我们更加直观

routes配置讲解

  • id ---> 区分唯一值
  • uri ---> 要转发的地址
    • http://127.0.0.1:8888 写死了地址,只能调用一个服务
    • lb:/nacos-provider-example #负载均衡
  • Predicate ---> 断言,即判断某个路由是否跟你规定的内容相匹配匹配就放行,当然我们还可以自定义自己的判断规则
    • 自定义predicate
    1. 凡是自定义predicate可以继承AbstractRoutePredicateFactory类
    2. 命名规范: 功能名+RoutePredicateFactory,如果定义token参数拦截的predicate命名为TokenRoutePredicateFactory
    3. 自定义类内部一般需要实现apply接口,创建自定义存参对象,
    4. 在配置文件内引入有两种方式:(都是在predicates属性下加入,以TokenRoutePredicateFactory为例)
        方式一:(该方式不支持list数据结构)
          - Token=arg1,arg2
        方式二:
          - name: Token
           args:
            name: token
            desc: token-demo
    5. 系统提供的predicate,我就不一一介绍了


      springCloud Gateway讲解_第4张图片
      image
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

@Component
public class TokenRoutePredicateFactory  extends AbstractRoutePredicateFactory {

    public TokenRoutePredicateFactory() {
        super(TokenConfig.class);
    }
    //要告诉转换为list,默认转换为map数据类型
    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    //重写方法,将args的参数转换到那个list中("Config中指定的list容器命名"),如果指定,会匹配不到,所以一下mothods会没有值
    @Override
    public List shortcutFieldOrder() {
        return Collections.singletonList("methods");
    }
    //自定义判断逻辑
    @Override
    public Predicate apply(TokenConfig config) {
        return ((param)-> {
            if (!CollectionUtils.isEmpty(config.methods) && config.methods.stream().anyMatch(method->method.equalsIgnoreCase(param.getRequest().getMethod().name()))){
                return true;
            }
            return false;
        });
    }

    static class TokenConfig{
        private List methods = new ArrayList();

        public List getMethods() {
            return methods;
        }

        public void setMethods(List methods) {
            this.methods = methods;
        }
    }
}

  • filter ---> 网关经常需要对路由请求进行过滤,进行一些操作,如鉴权之后构造头部之类的,过滤的种类很多,如增加请求头、增加请求 参数 、增加响应头和断路器等等功能

    • 系统内置的过滤类,这里也不一一介绍,可以去官方文档学些下:
    https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#gatewayfilter-factories
    
    • 全局filter,比如在网关中拦截token判断该token有效性,所有我们需要自定义一个全局filter,需要实现GlobalFilter
    /**
     * Token 校验全局过滤器
     */
    @Component
    public class AuthorizeFilter implements GlobalFilter, Ordered {
    
        private static final Logger log = LoggerFactory.getLogger( AuthorizeFilter.class );
    
        private static final String AUTHORIZE_TOKEN = "token";
    
        //自定义过滤的逻辑
        @Override
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.getRequest().getQueryParams().getFirst( AUTHORIZE_TOKEN );
            if ( StringUtils.isBlank( token )) {
                log.info( "token is empty ..." );
                exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED );
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    • 自定filter过滤工厂,和predicate工厂很相似,命名规范是一样的: 过滤名字+GatewayFilterFactory
    @Component
    public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory {
    
    private static final String COUNT_START_TIME = "countStartTime";
    
    //这里也是跟config中的值做绑定
    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }
    
    public CustomerGatewayFilterFactory() {
        super(Config.class);
        log.info("Loaded GatewayFilterFactory [CustomerGatewayFilterFactory]");
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (!config.isEnabled()) {
                //如果需要自定义返回一些内容
                final ServerHttpResponse response = exchange.getResponse();
                byte[] bytes = "{\"code\":\"99999\",\"message\":\"非法访问,没有检测到token~~~~~~\"}".getBytes(StandardCharsets.UTF_8);
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                //设置请求头,不然会乱码
                response.getHeaders().set("content-type","charset=utf-8")
                response.writeWith(Flux.just(buffer));
                return chain.filter(exchange);
            }
            exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(COUNT_START_TIME);
                        if (startTime != null) {
                            StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                    .append(": ")
                                    .append(System.currentTimeMillis() - startTime)
                                    .append("ms");
                            sb.append(" params:").append(exchange.getRequest().getQueryParams());
                            log.info(sb.toString());
                        }
                    })
            );
        };
    }
    
    public static class Config {
        /**
         * 控制是否开启统计
         */
        private boolean enabled;
    
        public Config() {}
    
        public boolean isEnabled() {
            return enabled;
        }
    
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
    }
    

    yaml配置

    filters:
    - Customer=true
    

以上就是springcloud gateway 详细的配置讲解了写了3天,从0-1学习真的难,首先自己找资料,写demo做测试. 然后把成功的方案在脑子里面编辑,怎么写到博客上面,希望能帮助大家,不懂可以留言大家一起交流,或者加我wx: 770219891

你可能感兴趣的:(springCloud Gateway讲解)