zoukankan      html  css  js  c++  java
  • Spring Cloud 微服务五:Spring cloud gateway限流

    前言:在互联网应用中,特别是电商,高并发的场景非常多,比如:秒杀、抢购、双11等,在开始时间点会使流量爆发式地涌入,如果对网络流量不加控制很有可能造成后台实例资源耗尽。限流是指通过指定的策略削减流量,使到达后台实例的请求在合理范围内。本章将介绍spring cloud gateway如何实现限流。

    前情回顾请参考:

    Spring Cloud 微服务一:Consul注册中心

    Spring Cloud 微服务二:API网关spring cloud zuul

    Spring Cloud 微服务三: API网关Spring cloud gateway

    Spring Cloud 微服务四:熔断器Spring cloud hystrix

    • 限流算法
          主流的限流算法有两种:漏桶(leaky bucket)和令牌桶(token bucket)。漏桶算法 有一个固定容量的桶,对于流入的水无法预计速率,流出的水以固定速率,当水满之后会溢出。

      令牌桶算法,有一个固定容量的桶,桶里存放着令牌(token)。桶最开始是空的,token以一个固定速率向桶中填充,直到达到桶的容量,多余的token会被丢弃。每当一个请求过来时,都先去桶里取一个token,如果没有token的话请求无法通过。

     

             两种算法的最主要区别是令牌桶算法允许一定流量的突发,因为令牌桶算法中取走token是不需要时间的,即桶内有多少个token都可以瞬时拿走。基于这个特点令牌桶算法在互联网企业中应用比较广泛,我们在实现限流的时候也会基于这个算法。

    • gateway如何实现限流
    •  方法1:Spring cloud gateway实现限流的方式主要是通过添加自定义filter来实现,自定义filter需要实现GatewayFilter和Ordered接口。本章将结合开源的Bucket4j来实现,Bucket4j是基于令牌桶算法实现,Bucket4j代码参考:https://github.com/vladimir-bukhtoyarov/bucket4j
        首先修改api-gateway module,pom中添加Bucket4j依赖,最新版本是4.3.0,工程的版本已经在父工程中定义好了
    <dependency>
           <groupId>com.github.vladimir-bukhtoyarov</groupId> 
           <artifactId>bucket4j-core</artifactId> 
    </dependency>

                    第二步,添加filter实现GatewayFilter和Ordered,添加相应的参数,并使用一个ConcurrentHashMap存储ip以及bucket,实现filter方法,对客户端访问ip进行过滤

    public class LimitFilter implements GatewayFilter, Ordered {
        private static final Logger logger = LoggerFactory.getLogger(LimitFilter.class);
    
        private int capacity;
        private int refillTokens;
        private Duration refillDuration;
    
        public LimitFilter(int capacity, int refillTokens, Duration refillDuration) {
            this.capacity = capacity;
            this.refillTokens = refillTokens;
            this.refillDuration = refillDuration;
        }
    
        private static final Map<String, Bucket> CACHE = new ConcurrentHashMap<>();
    
        private Bucket createNewBucket() {
            Refill refill = Refill.greedy(refillTokens, refillDuration);
            Bandwidth limit = Bandwidth.classic(capacity, refill);
            return Bucket4j.builder().addLimit(limit).build();
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            Bucket bucket = CACHE.computeIfAbsent(ip, k -> createNewBucket());
            logger.info("IP: "+ip+", available tokens :"+bucket.getAvailableTokens());
            if (bucket.tryConsume(1L)) {
                return chain.filter(exchange);
            }
            logger.info("IP: "+ip+", available tokens :"+bucket.getAvailableTokens()+" too many requests");
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return exchange.getResponse().setComplete();
    
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }

                 第三步,添加自定义路由,添加配置类,RouteLocator构造器中添加filter以及相应的地址信息,设置同一ip同时只能访问一次,多余的将被忽略。另外,由于我们在程序中配置了路由,需要将application.yml中的gateway相关属性删除。

    @Configuration
    public class RouteLocatorConfig {
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
            System.out.println("============================RouteLocatorConfig "+ builder.routes());
            return builder
                    .routes()
                    .route(r -> r.path("/*")
                            .filters(f -> f.filter(new LimitFilter(1,
                                    1, Duration.ofSeconds(1))))
                            .uri("http://localhost:10080/")
                            .order(0)
                            .id("user_route"))
                    .build();
        }
    }

             最后,测试,重启api-gateway,访问http://localhost:8088/users,第一次访问成功,频繁刷新会出现空白页,控制台会输出相关信息

                 方法2:使用spring cloud 原生的redis方式

        第一步,搭建redis服务器,具体方法参考redis官网

        第二步,pom中添加redis依赖

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

        自定义resolver

    @Configuration
    public class CustomResolver {
        @Bean
        public KeyResolver ipKeyResolver(){
            System.out.println("##############ipKeyResolver########################");
            return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    }

      第三步,修改application.yml配置

     routes:
            - id: user_route
              uri: http://localhost:10080
              predicates:
                - Path=/*
              filters:
                - name: RequestRateLimiter
                  args:
                    redis-rate-limiter.replenishRate: 1
                    redis-rate-limiter.burstCapacity: 1
                    key-resolver: "#{@ipKeyResolver}

            最后做测试,并使用monitor命令监控redis

  • 相关阅读:
    spring Bean的生命周期
    java合并两个有序数组的算法(抛砖引玉)
    Spring 中解析 URL参数的几种方式
    联合索引和单个索引使用注意事项
    Java中同一个类中不同的synchronized方法是否可以并发执行?
    简析JVM GC的根搜索算法
    spring rest 请求怎样添加Basic Auth请求頭
    spring boot 排除个别配置类的代码
    使用非对称算法RSA实现加解密和使用签名算法SHA1WithRSA、MD5withRSA生成签名以及验签
    Redis中如何发现并优化big key?
  • 原文地址:https://www.cnblogs.com/csts/p/10286378.html
Copyright © 2011-2022 走看看