API接口限流

嗨,大家好!今天给大家分享一个高并发系统中必备的技能——限流。

在正式开始之前我想问问大家,身为程序员的大家,在周一到周五早上上班进地铁站的时候,有没有见过类似这样的场景?

同样,在互联网行业中,也存在这样得场景,我们把它称为——限流,为什么要限流呢,原因如下:

在系统上线初期,用户量和访问量不大得时候,一般部署几台应用服务器,数据库做一个读写分离就基本上抗得住,但随着时间的推移,业务的发展,用户量和日活得增加,系统所承受的压力越来越大,我么都知道,应用服务器扩容很方便,但数据库扩容就有些麻烦,如果请求数据库的量太大的话,或者遇到恶意攻击,数据库极有可能宕机,最后导致整个网站不可用。

为了避免这样的事情发生,我们通常要限制住用户的请求次数。对于合法的用户,我们要限制单位时间内用户调用接口的次数,对于恶意攻击者,直接将他放入黑名单中。

我们通常用Redis中的zset数据结构来做限流。由于zset底层是用跳跃表存储数据的,按score字段从小到大排序,我们可以把时间戳存入score字段。随着时间的流逝,把每一次请求的时间戳存入score中,而member呢?随便存储什么无所谓啦,但不要存储太大的数据。如下图:

 

假如我们要求接口的调用一分钟不能超过100次,那么图中橙色矩形区域是一个时间段范围内的时间窗口,矩形的有边框是当前时间,它随着时间的流逝一点一点向右移动,矩形左边框是1min前,矩形的宽度就是1min。我们通过Redis的zcount命令很容易计算出这个时间范围内的数据量,如果数据量超出100,说明用户请求的太快了,应该进行限流的操作。下面来看一段简单的代码:

/**
     * 基于zset限流
     * @param key       redis key
     * @param maxCount  指定时间内最大通过个数
     * @param timeRange 时间窗范围,单位:秒
     * @return          是否可以继续请求
     */
    public static  boolean rateLimiterByZset(String key,int maxCount,int timeRange){
        try (Jedis jedis = jedisPool.getResource()) {
            long currentTime = new Date().getTime();        //当前时间戳
            long secondTime = currentTime-timeRange*1000;      //second秒前的时间戳
            long memberCount = jedis.zcount(key,secondTime,currentTime);
            if(memberCount >= maxCount){
                return false;
            }

            jedis.zadd(key,currentTime,currentTime+"");
            //删除时间框外的数据,因为它们已经没有用了
            jedis.zremrangeByScore(key,0,secondTime);
        }
        return true;
    }

哈哈,就这么简单。有一点要注意的是,请大家及时删除时间框(也就是上图中左边框外)范围外的数据,因为它们已经没有用了,留着非常消耗内存资源。

大家会不会有这样的疑问:对zset操作这么频繁,会不会有性能上的问题呀?其实大家不必太担心,毕竟Redis的数据结构都是经过精心设计的,性性能很高,大家可以参考小编的这篇文章,来学习Redis的数据结构。

测试程序和输出效果如下:

@Test
    public void test1() throws Exception{
        int i = 1;
        while(true){
            Thread.sleep(5);
            boolean flag = RedisRateLimiter.rateLimiterByZset("keysss",10,1);
            if(flag){
                System.out.println("第"+i+"个请求成功");
            }else{
                System.out.println("第"+i+"个被限流");
            }
            i++;
        }
    }

第1个请求成功
第2个请求成功
第3个请求成功
第4个请求成功
第5个请求成功
第6个请求成功
第7个请求成功
第8个请求成功
第9个请求成功
第10个请求成功
第11个被限流
第12个被限流
...
第42个被限流
第43个被限流
第44个请求成功
第45个请求成功

好了,这次的内容很简单,小编最近一直忙工作上的事情,有空再为大家分享更多有趣的东西

猜你喜欢

转载自blog.csdn.net/nuoWei_SenLin/article/details/109103280