限流(二)接口限流

如果某个接口可能出现突发情况,比如“秒杀”活动,那么很有可能因为突然爆发的访问量造成系统奔溃,我们需要最这样的接口进行限流。

在上一篇“限流算法”中,我们简单提到了两种限流方式:

1)(令牌桶、漏桶算法)限速率,例如:每 5r/1s = 1r/200ms 即一个请求以200毫秒的速率来执行;

2)(计数器方式)限制总数、或者单位时间内的总数,例如:设定总并发数的阀值,单位时间总并发数的阀值。

一、限制总并发数

我们可以采用java提供的atomicLong类来实现

atomicLong在java.util.concurrent.atomic包下,它直接继承于number类,它是线程安全的。

我们将使用它来计数

public class AtomicDemo {
    // 计数
    public static AtomicLong atomicLong = new AtomicLong(0L);
    // 最大请求数量
    static int limit = 10;
    // 请求数量
    static int reqAmonut = 15;

    public static void main(String[] args) throws InterruptedException {
        // 多线程并发模拟
        final CountDownLatch latch = new CountDownLatch(1);
        for (int i = 1; i <= reqAmonut; i++) {
            final int t = i;
            new Thread(new Runnable() {

                public void run() {
                    try {
                        latch.await();
                        // 计数器加1,并判断最大请求数量
                        if (atomicLong.getAndIncrement() > limit) {
                            System.out.println(t + "线程:限流了");
                            return;
                        }
                        System.out.println(t + "线程:业务处理");
                        // 休眠1秒钟,模拟业务处理
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 计数器减1
                        atomicLong.decrementAndGet();
                    }
                }
            }).start();
        }
        latch.countDown();
    }
}
 

二、限制单位时间的总并发数

下面用谷歌的Guava依赖中的Cache(线程安全)来完成单位时间的并发数限制,

Guava需要引入依赖:

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

具体逻辑如下:

1)根据当前的的时间戳(秒)做key,请求计数的值做value;

2)每个请求都通过时间戳来获取计数值,并判断是否超过限制。(即,1秒内的请求数量是否超过阀值)

代码如下:

public class AtomicDemo2 {
    // 计数
    public static AtomicLong atomicLong = new AtomicLong(0L);
    // 最大请求数量
    static int limit = 10;
    // 请求数量
    static int reqAmonut = 15;

    public static void main(String[] args) throws InterruptedException {
        // Guava的Cache来存储计数器

        final LoadingCache<Long, AtomicLong> counter = CacheBuilder.newBuilder()                                  .expireAfterWrite(1, TimeUnit.SECONDS)                                  .build(new CacheLoader<Long, AtomicLong>(){
                                        @Override
                                        public AtomicLong load(Long key) throws Exception {
                                             return new AtomicLong(0L);
                                        }
                                     });

        // 多线程并发模拟
        final CountDownLatch latch = new CountDownLatch(1);
        for (int i = 1; i <= reqAmonut; i++) {
            final int t = i;
            new Thread(new Runnable() {

                public void run() {
                    try {
                        latch.await();
                        long currentSeconds = System.currentTimeMillis()/1000;
                        // 从缓存中取值,并计数器+1
                        if (counter.get(currentSeconds).getAndIncrement() > limit) {
                            System.out.println(t + "线程:限流了");
                            return;
                        }
                        System.out.println(t + "线程:业务处理");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        latch.countDown();
    }
}
 

三、限制接口的速率

以上两种以较为简单的计数器方式实现了限流,但是他们都只是限制了总数。也就是说,它们允许瞬间爆发的请求达到最大值,这有可能导致一些问题。

下面我们将使用Guava的 RateLimiter提供的令牌桶算法来实现限制速率,例如:1r/200ms

同样需要引入依赖:

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

示例代码:

public class GuavaDemo {
    // 每秒钟5个令牌
    static RateLimiter limiter = RateLimiter.create(5);

    public static void main(String[] args) throws InterruptedException {
        final RateLimiter limiter2 = RateLimiter.create(5);
        for (int i = 0; i < 20; i++) {
            System.out.println(i + "-" + limiter2.acquire());
        }
    }
}
  

说明:

1)RateLimiter.create(5)表示创建一个容量为5的令牌桶,并且每秒钟新增5个令牌,也就是每200毫秒新增1个令牌;

2)limiter2.acquire() 表示消费一个令牌,如果桶里面没有足够的令牌那么就进入等待。

输出:

0.0
0.197729
0.192975
...

平均 1r/200ms的速率处理请求

RateLimiter允许突发超额,例如:

public class GuavaDemo {
    // 每秒钟5个令牌
    static RateLimiter limiter = RateLimiter.create(5);

    public static void main(String[] args) throws InterruptedException {
        final RateLimiter limiter2 = RateLimiter.create(5);
        System.out.println(limiter2.acquire(10));
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
    }
}

输出:

0.0
1.997777
0.194835
0.198466
0.195192
0.197448
0.196706

我们看到:

limiter2.acquire(10)

超额消费了10个令牌,而下一个消费需要等待超额消费的时间,所以等待了近2秒钟的时间,而后又开始匀速处理请求

由于上面的方式允许突发,很多人可能担心这种突发对于系统来说如果扛不住可能就造成崩溃。那针对这种情况,大家希望能够从慢速到匀速地平滑过渡。Guava当然也提供了这样的实现:

public class GuavaDemo {
    // 每秒钟5个令牌
    static RateLimiter limiter = RateLimiter.create(5);

    public static void main(String[] args) throws InterruptedException {
        final RateLimiter limiter2 = RateLimiter.create(5, 1, TimeUnit.SECONDS);
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
        System.out.println(limiter2.acquire());
    }
}

输出:

0.0
0.51798
0.353722
0.216954
0.195776
0.194903
0.194547

我们看到,速率从0.5慢慢地趋于0.2,平滑地过渡到了匀速状态。

RateLimter 还提供了tryAcquire()方法来判断是否有够的令牌,并即时返回结果,如:

public class GuavaDemo {
    public static void main(String[] args) throws InterruptedException {
        final RateLimiter limiter = RateLimiter.create(5, 1, TimeUnit.SECONDS);
        for (int i = 0; i < 10; i++) {
            if (limiter.tryAcquire()) {
                System.out.println("处理业务");
            }else{
                System.out.println("限流了");
            }
        }
    }
}

输出:

处理业务
限流了
限流了
限流了
限流了
限流了
限流了
限流了
限流了
限流了

以上,就是单实例系统的应用级接口限流方式。

参考:

http://jinnianshilongnian.iteye.com/blog/2305117

原文地址:https://www.cnblogs.com/lay2017/p/9062326.html

时间: 2024-11-05 20:32:12

限流(二)接口限流的相关文章

SpringCloud(8)----zuul权限校验、接口限流

项目代码GitHub地址:https://github.com/yudiandemingzi/spring-cloud-study 一.权限校验搭建 正常项目开发时,权限校验可以考虑JWT和springSecurity结合进行权限校验,这个后期会总结,这里做个基于ZuulFilter过滤器进行一个简单的权限校验过滤. 对于组件zuul中,其实带有权限认证的功能,那就是ZuulFilter过滤器.ZuulFilter是Zuul中核心组件,通过继承该抽象类,覆写几个关键方法达到自定义调度请求的作用

Guava的RateLimiter实现接口限流

最近开发需求中有需要对后台接口进行限流处理,整理了一下基本使用方法. 首先添加guava依赖: <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> 然后封装RateLimiter适用对多接口的限制: import com.goog

java接口限流算法

0. 前言 常见的限流算法有:令牌桶.漏桶.计数器也可以进行粗暴限流实现. 1. 算法介绍 1.1 令牌桶算法 令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌.令牌桶算法的描述如下: 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌: 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝: 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上: 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待). 1.

【Dnc.Api.Throttle】适用于.Net Core WebApi接口限流框架

Dnc.Api.Throttle    适用于Dot Net Core的WebApi接口限流框架 使用Dnc.Api.Throttle可以使您轻松实现WebApi接口的限流管理.Dnc.Api.Throttle支持IP.用户身份.Request Header.Request QueryString等多种限流策略,支持黑名单和白名单功能,支持全局拦截和单独Api拦截. Dnc.Api.Throttle暂时只支持Redis作为缓存和存储库,后续会进行扩展. 开始使用 安装Dnc.Api.Thrott

使用google的guova开发高并发下的接口限流

使用google的guova进行限流 1.guova的限流方式,在定时产生定量的令牌,令牌的数量限制了流量 2.增加一个订单接口限流类OrderRateFilter,继承ZuulFilter,并重载方法:filterType.filterOrder.shouldFilter.run ????filterType中return PRE_TYPE; ????fileterOrder中应该优先级最高,设为-4 ????shouldFilter中设置限流的方法(类似于鉴权) ???? RequestCo

Guava-RateLimiter实现令牌桶控制接口限流方案

一.前言 限流的目的是通过对并发/一个时间窗口内的请求进行限速来达到保护系统的效果,一旦达到限制速率则可以拒绝服务.排队或等待.降级等处理. 二.常见限流方案   原理 优点 缺点 计数器法 在单位时间段内,对请求数进行计数,如果数量超过了单位时间的限制,则执行限流策略,当单位时间结束后,计数器清零,这个过程周而复始,就是计数器法. null 不能均衡限流,在一个单位时间的末尾和下一个单位时间的开始,很可能会有两个访问的峰值,导致系统崩溃.   漏桶算法                     

.Net Core的API网关Ocelot的使用(二)[负载,限流,熔断,Header转换]

网关的负载均衡 当下游拥有多个节点的时候,我们可以用DownstreamHostAndPorts来配置 { "UpstreamPathTemplate": "/Api_A/{controller}/{action}", "DownstreamPathTemplate": "/api/{controller}/{action}", "DownstreamScheme": "https",

Java基础学习笔记二十 IO流

转换流 在学习字符流(FileReader.FileWriter)的时候,其中说如果需要指定编码和缓冲区大小时,可以在字节流的基础上,构造一个InputStreamReader或者OutputStreamWriter,这又是什么意思呢? OutputStreamWriter类 查阅OutputStreamWriter的API介绍,OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流中的字符编码成字节.它的作用的就是,将字符串按照指定的编码表转成字节,

io系列之常用流二

一.对象的序列化.持久化. 将java的对象的基本数据类型和图形存入文件中,实现对象数据的序列化和持久化. 操作对象可以使用: ObjectOutPutStream 和 ObjectInPutStream , 而被操作的对象: 必须实现  Serializable 接口(标记接口) ObjectOutPutStream: 构造方法: ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream. 一般方