接口中的几种限流实现

简介:

为什么需要限流

按照服务的调用方,可以分为以下几种类型服务

 

1、与用户打交道的服务

比如web服务、对外API,这种类型的服务有以下几种可能导致机器被拖垮:

用户增长过快(这是好事)

因为某个热点事件(微博热搜)

竞争对象爬虫

恶意的刷单

这些情况都是无法预知的,不知道什么时候会有10倍甚至20倍的流量进来,如果遇到此类情况,扩容是根本来不及的,弹性扩容也是来不及的;

 

2、对内的RPC服务

一个服务A的接口可能被BCDE多个服务进行调用,在B服务发生突发流量时,直接把A服务给调用挂了,导致A服务对CDE也无法提供服务。 这种情况时有发生,解决方案有两种: 1、每个调用方采用线程池进行资源隔离 2、使用限流手段对每个调用方进行限流

 

限流算法实现

常见的限流算法有:计数器、令牌桶、漏桶。

 

1、计数器算法

采用计数器实现限流有点简单粗暴,一般我们会限 制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。

 

具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。

 

这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”

 

2、漏桶算法

为了消除"突刺现象",可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。

 

不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。

在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。

这种算法,在使用过后也存在弊端:无法应对短时间的突发流量。

 

3、令牌桶算法

从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限 制请求调用的速率,而令牌桶算法能够在限 制调用的平均速率的同时还允许一定程度的突发调用。

 

在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。

 

放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。

 

幸运的是,通过Google开源的guava包,我们可以很轻松的创建一个令牌桶算法的限流器。


    com.google.guava
    guava
    18.0

 

通过RateLimiter类的create方法,创建限流器。

public class RateLimiterMain {

    public static void main (String[] args) {
        RateLimiter rateLimiter = RateLimiter.create(10);
        for (int i=0; i<10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    rateLimiter.acquire();
                    System.out.println("ok");
                }
            }).start();
        }
    }
}

其实Guava提供了多种create方法,方便创建适合各种需求的限流器。在上述例子中,创建了一个每秒生成10个令牌的限流器,即100ms生成一个,并最多保存10个令牌,多余的会被丢弃。

 

rateLimiter提供了acquire()和tryAcquire()接口 1、使用acquire()方法,如果没有可用令牌,会一直阻塞直到有足够的令牌。 2、使用tryAcquire()方法,如果没有可用令牌,就直接返回false。 3、使用tryAcquire()带超时时间的方法,如果没有可用令牌,就会判断在超时时间内是否可以等到令牌,如果不能,就返回false,如果可以,就阻塞等待。

 

集群限流

前面讨论的几种算法都属于单机限流的范畴,但是业务需求五花八门,简单的单机限流,根本无法满足他们。

 

比如为了限 制某个资源被每个用户或者商户的访问次数,5s只能访问2次,或者一天只能调用1000次,这种需求,单机限流是无法实现的,这时就需要通过集群限流进行实现。

 

如何实现?为了控制访问次数,肯定需要一个计数器,而且这个计数器只能保存在第三方服务,比如redis。

 

大概思路:每次有相关操作的时候,就向redis服务器发送一个incr命令,比如需要限 制某个用户访问/index接口的次数,只需要拼接用户id和接口名生成redis的key,每次该用户访问此接口时,只需要对这个key执行incr命令,在这个key带上过期时间,就可以实现指定时间的访问频率。

相关文章
|
21天前
|
NoSQL fastjson Redis
自定义限流注解@RateLimiter
自定义限流注解@RateLimiter
27 2
|
6月前
|
存储 算法 NoSQL
接口限流
防止用户恶意刷新接口, 防止对接口的恶意请求,减少不必要的资源浪费,从而保证服务可用。如果不做限流,任由某个用户以非正常方式高频率访问的话,会直接将网络流量、服务器资源挤占完,从而影响正常对外提供服务,造成服务不可用。
75 1
|
11月前
|
算法 NoSQL JavaScript
服务限流,我有6种实现方式…
服务限流,我有6种实现方式…
|
算法 NoSQL API
限流功能的实现
限流功能的实现
152 0
|
缓存 NoSQL 算法
限流实现-专题一
在实际业务中,经常会碰到突发流量的情况。如果公司基础架构做的不好,服务无法自动扩容缩容,在突发高流量情况下,服务会因为压力过大而崩溃。更恐怖的是,服务崩溃如同多米诺骨牌,一个服务出问题,可能影响到整个公司所有组的业务。
|
缓存 算法 网络协议
限流实现2
剩下的几种本来打算能立即写完,没想到一下三个月过去了,很是尴尬。本次主要实现如下两种算法 - 令牌桶算法 - 漏斗算法
|
监控 算法 安全
限流
1. 为什么需要限流 2. 如何限流 限流主要就是考虑这两点
210 0
限流
|
Java Maven 数据安全/隐私保护
使用 Sentinel 实现接口限流
使用 Sentinel 实现接口限流
|
算法
​什么是限流,如何限流
​什么是限流,如何限流
241 0
​什么是限流,如何限流
|
API Sentinel
使用 Sentinel 实现接口限流(下)
在前面一篇文章我已经对 Sentinel 做了一个简单的介绍,相信大家都有一个简单的了解,本次主要是讲述 Sentinel 的使用。在这个过程中我会讲到通过控制台配置流控规则,整合 RestTemplate 进行流控,配合 OpenFeign 进行流控三种 Sentinel 三种使用场景。
278 0