栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringCloud第四课(gateway网关详解、gateway统一鉴权、令牌桶算法、基于Filter限流、基于Sentinel限流、网关高可用)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringCloud第四课(gateway网关详解、gateway统一鉴权、令牌桶算法、基于Filter限流、基于Sentinel限流、网关高可用)

SpringCloud第四课(gateway网关详解、gateway统一鉴权、令牌桶算法、基于Filter限流、基于Sentinel限流、网关高可用) 前言

上文文章(Hystrix监控平台、DashBoard、Turbine监控、熔断器的状态、微服务网关Zuul)

微服务网关GateWay Zuul网关存在的问题

在实际使用中我们会发现直接使用Zuul会存在诸多问题,包括:

  • 性能问题

Zuul1x版本本质上就是一个同步Servlet,采用多线程阻塞模型进行请求转发。简单讲,每来
一个请求,Servlet容器要为该请求分配一个线程专门负责处理这个请求,直到响应返回客户
端这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被
阻塞,阻塞期间线程资源被占用,不能干其它事情。我们知道Servlet容器线程池的大小是有
限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成 容器无法接受新的请求。

  • 不支持任何长连接,如websocket
Zuul网关的替换方案
  • SpringCloud Gateway

微服务网关GateWay简介

Zuul 1.x 是一个基于阻塞 IO 的 API Gateway 以及 Servlet;直到 2018 年 5 月,Zuul 2.x(基于
Netty,也是非阻塞的,支持长连接)才发布,但 Spring Cloud 暂时还没有整合计划。Spring Cloud
Gateway 比 Zuul 1.x 系列的性能和功能整体要好。


Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式,统一访问接口。SpringCloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix ZUUL,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。


核心概念
  • 路由(route) 路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一
    组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
  • 断言(predicates) Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是
    Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定
    义匹配来自Http Request中的任何信息,比如请求头和参数等。
  • 过滤器(filter) 一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,
    分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。
搭建gateway项目模块 创建项目导入依赖
 		
            org.springframework.cloud
            spring-cloud-starter-gateway
        

注意:

SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容。引入的限流组件是hystrix。redis底层不再使用jedis,而是lettuce
意思就是说不能存在web的启动器,不然会报错,如下


配置启动类
package cn.ebuy.gateway;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayServerApplication {

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

编写配置文件

创建 application.yml 配置文件

server:
  port: 9091
spring:
  application:
    name: ebuy-gateway #服务名称
  cloud:
    gateway:
      routes:
          # 第一种 不支持负载均衡 http://localhost:9091/product/816753
      - id: ebuy-product
        uri: http://127.0.0.1:6501
        predicates:
          - Path=/product
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getPath().toString()
        );
    }
    
//    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getHeaders().getFirst("X-Forwarded-For")
        );
    }
    
//    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getQueryParams().getFirst("user")
        );
    }
    //    @Bean
    public KeyResolver remoteAddressKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getRemoteAddress().getHostName()
        );
    }


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

注意:以上几种策略只能保存一种,并且beanname对应上面配置文件中的#{@beanName}。

测试 测试根据请求路径的限流

将@Bean的的注释标签去掉
然后重启服务
接着请求服务

正常请求:

然后你上面配置文件配置了限流规则,可以尝试快速刷新,可以看到如下:

即是请求地址被限流了!


测试基于用户的限流

同样打开userKeyResolver的@Bean标签,并将配置文件中的beanname修改为userKeyResolver(记得把上面的请求路径的@Bean注释)
然后重启服务

  • 不加user参数,直接报错

  • 添加user参数,未被限流的情况

  • 被限流的情况(尝试多次快速刷新)


其他例子不再展示(同上)


查看redis的信息

查看redis的信息

  • timestamp:存储的是当前时间的秒数,也就是System.currentTimeMillis() / 1000或者
    Instant.now().getEpochSecond()。
  • tokens:存储的是当前这秒钟的对应的可用的令牌数量

Spring Cloud Gateway目前提供的限流还是相对比较简单的,在实际中我们的限流策略会有很多种情
况,比如:

  • 对不同接口的限流
  • 被限流后的友好提示

这些可以通过自定义RedisRateLimiter来实现自己的限流策略,这里我们不做讨论


基于Sentinel的限流

Sentinel支持Spring Cloud Gateway、zuul等主流的API Gateway进行限流

图解:


从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限
流:

  • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
  • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则
和自定义 API 的实体和管理逻辑:

  • GatewayFlowRule :网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
  • ApiDefinition :用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api ,请求 path 模式为 /foo @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void initGatewayRules() { Set rules = new HashSet<>(); rules.add( new GatewayFlowRule("ebuy-product") //资源名称 服务名 .setCount(1) // 限流阈值 .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒 ); GatewayRuleManager.loadRules(rules); } }

    注: 配置初始化的限流参数中的rules是一个set集合,也就是说你可以加入多个服务名,为多个服务进行限流。(上面的代码只给ebuy-product配置了限流,你可以给ebuy-order等配置限流,前提是你的配置文件中配置了该服务的路由转发。)


    修改启动类

    注释掉前面配置的基于Filter的限流的Bean

    修改配置文件


    重启服务

    重启gateway网关服务之后,再次请求 未限流的状态:

    尝试快速多次刷新,如下:


    但是这样的限流提示太过不够人性化,所以sentinel提供了一种更加友好的限流提示配置,往下看!


    自定义异常提示

    当触发限流后页面显示的是Blocked by Sentinel: FlowException。为了展示更加友好的限流提示,
    Sentinel支持自定义异常处理。
    您可以在 GatewayCallbackManager 注册回调进行定制:

    • setBlockHandler :注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为BlockRequestHandler 。默认实现为 DefaultBlockRequestHandler ,当被限流时会返回类似于下面的错误信息: Blocked by Sentinel: FlowException 。

    在刚才的配置类中加入如下:

        @PostConstruct
        public void initBlockHandlers() {
            BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
                public Mono handleRequest(ServerWebExchange
                                                                  serverWebExchange, Throwable throwable) {
                    Map map = new HashMap<>();
                    map.put("code", 001);
                    map.put("message", "对不起,接口限流了");
                    return ServerResponse.status(HttpStatus.OK).
                            contentType(MediaType.APPLICATION_JSON_UTF8).
                            body(BodyInserters.fromObject(map));
                }
            };
            GatewayCallbackManager.setBlockHandler(blockRequestHandler);
        }
    


    再次重启服务测试

    此时限流提示为:


    参数限流

    顾名思义:就是不直接给整个微服务资源限流,而是拿到请求的某个参数,对参数进行规则限流。
    上面的配置是针对整个路由来限流的,如果我们只想对某个路由的参数做限流,那么可以使用参数限流
    方式:
    如下:

            rules.add(
                    new GatewayFlowRule("ebuy-product") //资源名称 服务名
                            .setCount(1) // 限流阈值
                            .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
                            .setParamItem(new GatewayParamFlowItem()
                                    .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                                    .setFieldName("id"))
            );
    

    通过指定PARAM_PARSE_STRATEGY_URL_PARAM表示从url中获取参数,setFieldName指定参数名称

    图解:


    重启服务测试

    不加限流参数(id),进行请求:

    不同于上面的基于Filter限流(不加限流参数直接报错),基于sentinel限流,参数配置之后不加参数不进行限流处理

    — 添加限流参数(id),进行快速多次刷新:

    添加参数之后,对其进行限流处理


    自定义API分组
        
        @PostConstruct
        private void initCustomizedApis() {
            Set definitions = new HashSet<>();
    
            //配置 产品服务分组
            ApiDefinition api1 = new ApiDefinition("product-api");
            api1.setPredicateItems(new HashSet() {{
                        add(new ApiPathPredicateItem().setPattern("/ebuy-product/product/**").
                                setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    }});
            definitions.add(api1);
            GatewayApiDefinitionManager.loadApiDefinitions(definitions);
        }
    

    图解:

    如需配置多个分组,可以创建多个ApiDefinition 然后配置配置初始化的限流参数,如下:


    接下来说一下网关的高可用。


    网关高可用

    由于微服务架构中,客户端一切的请求都要经过网关转发请求,所以网关的工作压力是非常大的,所以搭建集群尤其重要。

    高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。


    实现原理图:

    使用nginx反向代理我们的服务端

    我们实际使用 Spring Cloud Gateway 的方式如上图,不同的客户端使用不同的负载将请求分发到后端 的 Gateway,Gateway 再通过HTTP调用后端服务,最后对外输出。因此为了保证 Gateway 的高可用 性,前端可以同时启动多个Gateway 实例进行负载,在 Gateway 的前端使用 Nginx 或者 F5 进行负载 转发以达到高可用性。


    修改配置文件

    直接修改application.yml中的端口,启动两个gateway服务。

    如下:


    然后访问测试下,如下:

    配置nginx

    下载解压过程不再演示,可以看之前的文章(nginx文章)

    配置如下:

    代码如下:

    	upstream gateway{
    		server 127.0.0.1:9091;
    		server 127.0.0.1:9092;
    	}
            location / {
    			proxy_pass http://gateway;
            }
    

    然后启动nginx,如下:

    这样就启动成功了,nginx的默认端口是80

    测试

    访问服务,如下:

    测试关闭一个gateway服务,访问测试(过程不再展示)


    执行流程分析

    流程图:

    Spring Cloud Gateway 核心处理流程如上图所示,Gateway的客户端向 Spring Cloud Gateway 发送请求,请求首先被 HttpWebHandlerAdapter 进行提取组装成网关上下文,然后网关的上下文会传递到 DispatcherHandler 。
    DispatcherHandler 是所有请求的分发处理器, DispatcherHandler 主要
    负责分发请求对应的处理器。比如请求分发到对应的 RoutePredicateHandlerMapping (路由断言处
    理映射器)。路由断言处理映射器主要作用用于路由查找,以及找到路由后返回对应的FilterWebHandler 。 FilterWebHandler 主要负责组装Filter链并调用Filter执行一系列的Filter处理,然后再把请求转到后端对应的代理服务处理,处理完毕之后将Response返回到Gateway客户端。

    本文结束谢谢大家!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/287059.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号