单个应用的限流

应用限流

前言

对于一个高并发的系统来说,限流其实是非常重要的一个环节,当巨大的流量到达我们的服务器的时候,就很容易造成接口不可用。所以针对此类问题,本文对限流这个问题在业务代码层面进行深入探讨。

(七牛云的图床不知道为啥在Markdown里解析不出来,今天用富文本编辑器吧)

限流算法

1,计数器算法

算法如其名,对请求进行技术,超过了设定的上限,则拒绝请求,是比较粗暴简单的一种算法。比如设定上限为100,那么每来一次请求计数器就+1,当然这里的+1的过程肯定是原子性的,可以采用AtomicLong来实现,在接下里的一秒内,如果计数器的值达到了100,那么后面的请求全部被拒绝。

缺点:计数器只在请求数上进行了考虑,并没有考虑请求到来的速率,也就是说如果一秒钟前面的100mm请求数就到达了100,那么在后面的900mm中,请求就只能全部被拒绝,这种现象叫做“突刺现象”。那么这样会造成问题呢,从安全角度来思考,如果我们的应用设置一分钟最多100请求,此时有一个不怀好意的用户,在这一分钟的最后一秒钟就直接发送了100个请求,然后在下一分钟的一开始又瞬间发送了100个请求,这样其实已经大大超过了请求速率的上限,这样应用就不提供服务了,这里的缺点在于,对于秒级的限流,我们没有没有做到精度控制,也就是一分钟的时间却只有一个计数器,这样自然是不行的。

2,滑动窗口算法

这个在学习计网的时候都一定有所接触,实际上就是对固定的时间片进行划分并且随着时间进行移动,这种算法可以克服上面的计数器算法的临界点的问题,在滑动从窗口算法中,将时间段进行了划分,比如将一分钟均等划分为六个小格子,每一个小格子有自己独立的计数器并且负责自己这一个格子的计数。每过去十秒钟,窗口就会往后走动一格。对于计数器的临界问题,在滑动窗口中就不会有这个问题了,比如在前一分钟的最后一秒恶意用户突然发送了100个请求(下图灰色部分),到后一分钟的第一秒的时候(下图橘色部分),又来了100个请求,但是此时滑动窗口也向后面移动了一格,所以移动后的时间窗口内的总请求是200,应用就能检测到请求速率超高进而进行限流。

3, 漏桶算法

顾名思义,这个算法类似于漏斗,上面口大,下面口小,来得及处理的就从下面的口子出去,来不及的就先存在桶里,如果桶满了,请求就先丢弃。

图示:

​ 缺点:漏桶算法也有缺点,那就是不能应对短时间的突发流量。

4,令牌桶算法

令牌桶算法是对漏桶算法的一种改进,桶算法可以限制请求调用的速率,而令牌桶算法能够限制调用的平均速率,同时允许一定程度的突发调用。令牌桶算法会以一个恒定的速率向桶中放入令牌,每一次请求调用都需要获取令牌,只有获取到了,才有机会继续执行,当桶里没有令牌的时候,将当前请求丢弃或者阻塞。其中,放令牌的动作是不断地执行的,如果桶里的令牌数量达到上限,就丢弃令牌。所以这种算法可以抵挡瞬时增加的请求,只有在桶里没有令牌的时候,请求就会进行等待或者被丢弃。

令牌桶算法demo

针对生产中常用的限流方式,这里采用Guava中提供的RateLimiter来对令牌桶算法写个demo。代码理解起来也不难。

/**
 * @author yintianhao
 * @createTime 2020/10/18 16:54
 * @description
 */
@RestController
@Slf4j
public class DemoController {


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

    /**
     * rateLimiter 限流器
     *
     * */
    private static final RateLimiter rateLimiter = RateLimiter.create(10);


    @GetMapping("/get")
    public String getResponse(){

        if(rateLimiter.tryAcquire()){
            //一次拿一个,默认返回时间是0,即拿不到就立即返回
            log.info(String.format("time:%s msg:%s",sdf.format(new Date()),"请求正常"));
            try {
                Thread.sleep(500);
            }catch (Exception e){
                e.printStackTrace();
            }
            return "正常请求";
        }else{
            log.error(String.format("time:%s msg:%s",sdf.format(new Date()),"请求限流"));
            return "限流了";
        }
    }


}
DemoController

再用JMeter来进行测试,设置十个线程去请求。

查看限流情况:

原文地址:https://www.cnblogs.com/Yintianhao/p/13837959.html