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有效性,没效果直接返回
流量控制 --流量大的时候很容易导致服务器奔溃,所以需要控制流量
熔断 --当下游服务不可用时快速返回错误信息,而不是继续访问下游服务
日志监控 --谁访问了那个接口

架构图

image

怎么用

Gateway的三大概念

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

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

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

以上三个概念的流程图

image

springCloud Gateway工作流程图

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,我就不一一介绍了


      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<TokenRoutePredicateFactory.TokenConfig> {

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

    //重写方法,将args的参数转换到那个list中("Config中指定的list容器命名"),如果指定,会匹配不到,所以一下mothods会没有值
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("methods");
    }
    //自定义判断逻辑
    @Override
    public Predicate<ServerWebExchange> 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<String> methods = new ArrayList();

        public List<String> getMethods() {
            return methods;
        }

        public void setMethods(List<String> 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<Void> 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<CustomerGatewayFilterFactory.Config> {
    
    private static final String COUNT_START_TIME = "countStartTime";
    
    //这里也是跟config中的值做绑定
    @Override
    public List<String> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354