SpringCloud微服务网关做边缘服务限流方案

优质文章,第一时间送达

66套java从入门到精通实战课程分享

在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。

常见的限流方式,比如Hystrix适用线程池隔离,超过线程池的负载,走熔断的逻辑。在一般应用服务器中,比如tomcat容器也是通过限制它的线程数来控制并发的;也有通过时间窗口的平均速度来控制流量。常见的限流纬度有比如通过Ip来限流、通过uri来限流、通过用户访问频次来限流。

一般限流都是在网关这一层做,比如Nginx、Openresty、kong、zuul、Spring Cloud Gateway等;也可以在应用层通过Aop这种方式去做限流。这里主要讲讲常用的限流方式。
一、常见的限流算法
目前常用的限流算法有两个:漏桶算法和令牌桶算法。
1·计数器算法
计数器算法采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。具体的实现可以是这样的:对于每次服务调用,可以通过AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”
2.漏桶算法
漏桶算法的原理比较简单,请求进入到漏桶中,漏桶以一定的速率漏水。当请求过多时,水直接溢出。可以看出,漏桶算法可以强制限制数据的传输速度。但高并发情况下最容易出现瓶颈。

3.令牌桶算法
令牌桶算法的原理是系统以一定速率向桶中放入令牌,如果有请求时,请求会从桶中取出令牌,如果能取到令牌,则可以继续完成请求,否则等待或者拒绝服务。这种算法可以应对突发程序的请求,因此比漏桶算法好。

在Wikipedia上,令牌桶算法是这么描述的:

·每秒会有r个令牌放入桶中,或者说,每过1/r 秒桶中增加一个令牌
·桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃
·当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包
·如果桶中可用令牌小于n,则该数据包将被缓存或丢弃
Spring Cloud Gateway限流
在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现上述三种过滤器。但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用Redis和lua脚本实现了令牌桶的方式。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,lua脚本在如下图所示的文件夹中:

读者可以自行查看,先以案例的形式来讲解如何在Spring Cloud Gateway中使用内置的限流过滤器工厂来实现限流。

首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:

 <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>

在配置文件中做以下的配置:

server:  port: 8088spring:  cloud:    gateway:      routes:      - id: limit_route        uri: http://httpbin.org.com:80/get        predicates:        - After=2017-01-20T17:42:47.789-07:00[America/Denver]        filters:        - name: RequestRateLimiter          args:            key-resolver: '#{@hostAddrKeyResolver}'            redis-rate-limiter.replenishRate: 1            redis-rate-limiter.burstCapacity: 3  application:    name: gateway-limiter  redis:    host: localhost    port: 6379    database: 0

在上面的配置文件,指定程序的端口为8088,配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:

burstCapacity,令牌桶总容量。
replenishRate,令牌桶每秒填充平均速率。
key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
KeyResolver需要实现resolve方法,比如根据Hostname进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。

public class HostAddrKeyResolver implements KeyResolver {

@Override
public Mono<String> resolve(ServerWebExchange exchange) {
    return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}

}

@Bean
public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}

可以根据uri去限流,这时KeyResolver代码如下:

public class UriKeyResolver implements KeyResolver {

@Override
public Mono<String> resolve(ServerWebExchange exchange) {
    return Mono.just(exchange.getRequest().getURI().getPath());
}

}

@Bean
public UriKeyResolver uriKeyResolver() {
return new UriKeyResolver();
}

也可以以用户的维度去限流:

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

用jmeter进行压测,配置10thread去循环请求lcoalhost:8088,循环间隔1s。从压测的结果上看到有部分请求通过,由部分请求失败。通过redis客户端去查看redis中存在的key。

可见,RequestRateLimiter是使用Redis来进行限流的,并在redis中存储了2个key。关注这两个key含义可以看lua源代码。
使用Guava的RateLimiter做限流
Guava中开源出来一个令牌桶算法的工具类RateLimiter,可以轻松实现限流的工作。RateLimiter对简单的令牌桶算法做了一些工程上的优化,具体的实现是SmoothBursty。需要注意的是,RateLimiter的另一个实现SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter中的时间窗口能且仅能为1S,如果想搞其他时间单位的限流,只能另外造轮子。

RateLimiter有一个有趣的特性是[前人挖坑后人跳],也就是说RateLimiter允许某次请求拿走了超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止。这里面就涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,还是让它走掉后面的请求等一等呢?Guava的设计者选择的是后者,先把眼前的活干了,后面的事后面再说。

测试代码:

public class RateLimiterMain {
    public static void main(String[] args) {
        RateLimiter rateLimiter = RateLimiter.create(2);
        System.out.println(rateLimiter.acquire(5));
        System.out.println(rateLimiter.acquire(2));
        System.out.println(rateLimiter.acquire(1));
    }
}

输出内容:

0.0
2.496889
0.992149
可以看出,令牌桶每秒只能产生2个令牌,我们可以第一次取出5个,但是第二次再去取令牌的时候,需要等2.5s,也就是第一次令牌取完后,需要等2.5s才能取到令牌。同样的,第三次取1个令牌的时候,也需要等待第二次的1s的时间。也就是,取的速率可以超过令牌产生的速率,但是下一次再次去取的时候,需要阻塞等待。

当然也可以使用tryAcquire来非阻塞的获取,可以实时返回结果。另外tryAcquire也可以传入参数,也就是等待的时间,超时直接返回false。这点等同于常见的lock,tryLock。
并发控制Semapphore
一般来说,在网关系统中,还有一个参数叫并发控制,就是某一个资源可以被同时访问的个数。这种情况下,我们可以使用Semaphore来控制。

Semaphore不同于互斥锁。互斥锁是某个资源只能支持同时一个访问,而Semaphore可以支持多个访问,但是加上了总数的控制。

示例
4.1 在pom中加入guava依赖

<dependency>
 <groupId>com.google.guava</groupId>
 <artifactId>guava</artifactId>
 <version>18.0</version>
</dependency>

把限流服务封装到一个类中LimitService,提供tryAcquire()方法,用来尝试获取令牌,返回true表示获取到,如下所示:

@Servicepublic class LimitService {

    //每秒只发出5个令牌    RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**     * 尝试获取令牌     * @return     */    public boolean tryAcquire(){        return rateLimiter.tryAcquire();    }}

调用方是个普通的controller,每次收到请求的时候都尝试去获取令牌,获取成功和失败打印不同的信息,如下:

@Controller
public class HelloController {

private static SimpleDateFormat sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss');

@Autowired
    private LimitService limitService;

@RequestMapping('/access')
    @ResponseBody
    public String access(){
        //尝试获取令牌
        if(limitService.tryAcquire()){
            //模拟业务执行500毫秒
            try {
                Thread.sleep(500);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            return 'aceess success [' + sdf.format(new Date()) + ']';
        }else{
            return 'aceess limit [' + sdf.format(new Date()) + ']';
        }
    }
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

(0)

相关推荐

  • 分布式服务限流实战,已经为你排好坑了

    开头:本文由 dbaplus 社群授权转载. 一.限流的作用 由于 API 接口无法控制调用方的行为,因此当遇到瞬时请求量激增时,会导致接口占用过多服务器资源,使得其他请求响应速度降低或是超时,更有甚 ...

  • 稳定性五件套-限流的原理和实现

    背景 最近了解到很多朋友对限流.熔断.降级.隔离.超时重试的概念和应用场景理解的不是很到位,所以想用五篇的篇幅稍微系统的介绍一下. 本篇是第一篇,是限流做详解,如果反馈好的话,我会继续写下面四篇.不好 ...

  • 分布式与微服务

    0x01:分布式 CAP C:consistency 一致性 分布式系统能够同时访问同一份数据副本 A:availability 可用性 非故障节点能够在合理时间内获得合理的结果 P:Partitio ...

  • 何谓架构?

    前言:在这个知识分享的爆炸时代,鉴于java生态的完整和繁荣,各种框架.中间件和工具包供我们使用.连新培训出来的人都知道ssm,微服务.集群.多线程.队列.高并发等技术,技术的间隔性正变得越来越小,仿 ...

  • 服务熔断、隔离、降级、限流 介绍

    服务降级:在高并发的情况下,防止用户一直等待,使用服务降级方式进行处理(返回友好的提示给客户端,fallback回调方法).当服务不可用的时候(正在等待的时候.网络延迟.响应时间过长),客户端会处于一 ...

  • 6000多字 | 秒杀系统设计注意点【理论】

    在秒杀的场景中,对于系统的要求其实就三个字:快.准.稳. 本文主要内容: 五个架构原则 数据要尽量少 首先是指用户请求的数据能少就少.请求的数据包括上传给系统的数据和系统返回给用户的数据(通常就是网页 ...

  • Soul限流插件之RateLimiter插件

    从Soul的RateLimiter插件的配置可以看到 RateLimiter是依赖于Redis的,可以看到限流也可以基于Redis的三种模式的单机(standlone),集群(cluster)和哨兵( ...

  • 一文搞定算法和架构,微服务接口限流不用愁!

    目录1.服务限流的概念2.单服务节点限流2.1.漏桶算法2.2.令牌桶算法3.服务集群限流4.限流的难点及注意事项5.作者简介线上系统遇到的一大风险就是流量的暴涨暴跌,尤其是在这个全民上网的时代,一条 ...

  • 微服务和API网关限流熔断实现关键逻辑思路

    作者:人月神话,新浪博客同名 简介:多年SOA规划建设,私有云PaaS平台架构设计经验,长期从事一线项目实践 今天准备谈下微服务架构和API网关中的限流熔断,当前可以看到对于Spring Cloud框 ...

  • 通过API网关实现微服务管控-限流,熔断和降级

    今天准备谈下基于API网关来实现微服务治理管控中的服务限流,熔断和降级方面的内容.在前面谈微服务架构的时候也谈到过类似通过Hystrix,Sentinel来是服务限流熔断.包括也不断地在谈去中心化架构 ...

  • 通过Dapr实现一个简单的基于.net的微服务电商系统(七)——一步一步教你如何撸Dapr之服务限流

    曾宇平 dotNET跨平台 今天 在一般的互联网应用中限流是一个比较常见的场景,也有很多常见的方式可以实现对应用的限流比如通过令牌桶通过滑动窗口等等方式都可以实现,也可以在整个请求流程中进行限流比如客 ...

  • 微服务网关 zuul 替代者 gateway 网关路由

    简述 Spring Cloud Gateway 是 Spring Cloud 的一个子项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技 ...

  • SpringCloud微服务架构实战:微服务治理

    微服务治理 Spring Cloud 工具套件为微服务治理提供了全面的技术支持.这些治理工具主要包括服务的注册与发现.负载均衡管理.动态路由.服务降级和故障转移.链路跟踪.服务监控等.微服务治理的主要 ...

  • SpringCloud微服务开发实战:如何进行微服务的拆分?

    如何进行微服务的拆分 在前面介绍了基于Spring Boot来快速实现一个"天气预报"应用.虽然没有使用太多的代码,但已经实现了数据采集.数据缓存.提供天气查询等诸多的功能,这也是 ...

  • 阳光城:用数字化、产品力和服务力做“内循环” 谋局新十年

    "地产行业经历了有史来最困难的一年."放在几年前,如此言论会被视为危言耸听,可是到2020年房企已经感受到切肤之痛. 易居研究院统计数据显示,2020年66家典型房企营收达到4.5 ...

  • 多做奉献服务

    古如说:多做奉献服务,这是对所有问题的回答.