Redis限流-计数器

/ 0评 / 0

场景介绍:假如你有一个论坛类应用,活跃用户数逐渐增大,垃圾请求与越来越多,比如恶意的发帖、回帖、点赞等,尤其是当系统的处理性能有限时,面对大量的请求,如何去管理这些计划外的请求对系统继续施加压力?

场景分析:面对如此大量的请求,我们需要设计一个功能,将所有的用户请求进行严格的流量管控,限制指定时间内的请求次数,超过请求次数阈值之外的请求将会被丢弃。而如此大量的请求显然使用Redis来进行限流是比较合适的。

那么我们如何使用Redis进行限流呢?系统限定用户在指定时间的请求次数,我们假设一个场景是“jankin”用户的登录次数,限制60秒只能进行5次登录操作。那么我们来设计这个功能。

想法一:简单的计数器

使用string数据接口,将用户名和请求操作绑定到一起作为key,value显示其请求次数,每次请求将value自增1,然后给这个key设置一个失效时间。这时候就会遇到一个问题,这个用户在当前60秒的最后时刻进行了5次操作,操作完之后刚好失效,那么在新的60秒这个时间段里又可以继续请求,好像他在很短的时间内可以进行10次请求。

这时候就会发现问题,假如大量的用户都碰巧是这种情况,那么系统在某一时刻的压力会突然增大甚至崩溃,显然这是我们不能接受的。

想法二:滑动窗口法做限流

假设一个60秒的窗口,会随着时间的推移而改变开始值和结束值,不变的是窗口的长度——60秒。

而Redis中又如何进行实现这个滑动的窗口呢,想想zset数据结构的score值,是不是可以设置score的值为毫秒时间戳,这样就可以圈出来这个窗口了。这样我们可以通过Redis的zremrangebyscore方法砍掉窗口之外的数据,再用zcard计算窗口内的个数就知道当前存在多少请求啦!那么value要设置成什么呢?好像没有什么特殊的要求,只要不重复即可,为了方便可以直接使用毫秒时间戳。

话不多说,看图解,好像图画的很一般,那就假装画的很好!

图中的窗口无论怎样滑动,窗口里面都是最多只能容纳5个请求。下面我用go语言实现一遍,代码里详细写了注释,应该很好理解。

package main

import (
	"fmt"
	"github.com/go-redis/redis"
	"strconv"
	"time"
)

func main() {
	//创建Redis连接
	rdb := redis.NewClient(&redis.Options{
		Addr :"127.0.0.1:6379",
		Password:"",
		DB:0,
	})
	for i:=0;i<20;i++{
		//假设对用户jankin的登录操作进行限流检测,60秒内允许登录5次
		fmt.Println(isActionAllowed(*rdb,"jankin","login",60,5))
               //可以根据isActionAllowed方法返回的是true还是false来判断是否达到限流阈值
	}
}

func isActionAllowed(rdb redis.Client,userId,actionKey string,period,maxCount int) bool {
	millisecond := time.Now().UnixNano()/1e6
	key := userId+"_"+actionKey
	//裁剪zset,只保留窗口内的值
	rdb.ZRemRangeByScore(key,"0",strconv.FormatInt(millisecond-int64(period*1000),10))
	//统计窗口的值的数量
	ran := rdb.ZCard(key)
	//先判断是否达到阈值
	if int(ran.Val()) < maxCount {
		fmt.Println("当前未到达限流阈值,插入新数据:",millisecond)
		//符合阈值才添加数据
		rdb.ZAdd(key,redis.Z{
			Score:  float64(millisecond),
			Member: millisecond,
		})
		//设置一个过期时间,通常只要比窗口大一点即可,这里设置大1秒
		rdb.Expire(key,time.Duration(period+1)*time.Second)
		return  true
	}else {
		fmt.Println("当前已到达限流阈值")
		return  false
	}
}

当然是用Redis限流的主流方法还有漏桶算法(leaky-bucket)和令牌桶算法(token-bucket),本文主要讲解简单的计数器和滑动窗口法,这两种算法都属于计数器法,后面将有更详细的实验进行介绍漏桶算法和令牌桶算法。