常见限流方案

本文介绍了在系统访问量增大时,限流作为服务降级的一种手段,其原理和常见实现。讨论了固定窗口、滑动窗口、漏桶算法和令牌桶算法的优缺点,以及在不同场景下的适用性。
摘要由CSDN通过智能技术生成

一、概述

当系统的访问量增大的时候,一般会往这么几个方向上去优化:缓存,扩容,降级,限流。这里将聊一聊限流,限流其实可以看做是服务降级的一种实现。当请求超过系统的负载后,多余的请求做降级处理,怎么降级呢?就是限流,直接快速失败,不进行业务处理。现实生活中也有这种思想,比如高速公路车流量很大的时候,会将高速公路入口关闭,或者每隔一段时间放行一定的车辆,防止大量车辆进入给交通带来更大的压力,这就是一种限流的思想。

二、固定窗口

原理

固定窗口限流是限制在一段时间内的请求次数,当这一段时间过去之后,下一个一段时间才可以请求,但同样限制了下一个时间段内的请求次数。比如,限制1分钟访问10次,假设当前时间是9:00:00,那么在9:01:00之间允许10个请求,在9:01:00 ~ 9:02:00允许10个请求,依次类推,每一个一分钟的时间窗口内允许10次请求,可以看到这个时间窗口是一段一段。
在这里插入图片描述

实现

固定窗口的实现比较简单,借助Redis,以限流接口为key,设置1分钟的过期时间,执行Redis的incr方法,如果返回值大于限流次数,则快速失败,否则放行。

问题

这会带来突刺的问题,比如在9:01:59的时候突然请求了10次,在9:02:00也请求了10次,那么实际上在这两秒内的请求达到了20。在上一个限流窗口的尾部和下一个限流窗口的开头,容易出现突刺,导致局部时间范围内qps偏高。如下图所示。
在这里插入图片描述

三、滑动窗口

原理

上面的固定窗口容易出现突刺的问题,滑动窗口可以限制在任意的一段时间内,qps不超过预设值。假设有一个n秒的滑动窗口,它可以在时间轴上任意滑动,且保持滑动窗口内的请求数量不超过预设值x。
在这里插入图片描述

实现

借助Redis的zset,可以以限流接口为key,value为一个唯一值,score为时间戳。每次进入限流方法时,进行滑动窗口的整理,根据时间,也就是score,移除已经不在窗口范围内的元素,然后判断大小,如果窗口内元素的个数没有超过限制,那么允许访问,否则限流。参考代码如下。

package wanghang.tools.limit;

import wanghang.tools.util.RedisUtil;

import java.time.LocalTime;
import java.util.Random;

/**
 * createTime 2020/11/22 16:43
 * 滑动窗口限流
 * 限制在x秒内能执行n次操作
 *
 * @author wanghang
 * @version v1.0
 */
public class SlideWindowLimiter{
   

    /**
     * 滑动窗口大小,n毫秒的窗口
     */
    private int slideWindowSize;

    /**
     * 滑动窗口内允许的操作数
     */
    private int actionLimit;

    public SlideWindowLimiter(int slideWindowSize, int actionLimit) {
   
        this.slideWindowSize = slideWindowSize;
        this.actionLimit = actionLimit;
    }

    /**
     * 比如限制一个用户在30秒内只能评论10次。如果出现并发,这个实现是不可靠的。
     * 在redis中创建一个zset,key为uid + "comment",即每个用户在评论这个操作下对应一个zset,value为该用户评论这个行为标识,
     * 可以是用户id+行为+时间戳。
     * score是时间戳。表示当前评论时间。
     * 注意zset需要设置一个大于30秒的过期时间,长时间无评论操作,可视为用户下线,释放存储空间。
     * @param key key
     * @return 是否允许操作
     */
    public synchronized boolean isAllowed(String key) {
   
        long ts = System.currentTimeMillis();
        makeSpace(key, ts);
        if (size(key) < actionLimit) {
   
            recordAction(key, ts);
            return true;
        }
        return false;
    }

    /**
     * 记录用户行为
     *
     * @param key key
     * @param ts  ts
     */
    private void recordAction(String key, long ts) 
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值