Redis实现访问控制频率

摘要:
伪代码如下:$isKeyExists=EXISTRate。limiting:$userId//如果返回值为1,则如果$isKeyExists$times=INCRATE,则返回值为0。限制:$userIdif$times˃100//第100次访问将增加到101print。访问频率超过限制,请稍后重试exitelseMULTI/此处。如果没有交易,竞争条件可能显示为INCRATE。限制:$userId EXPIRE$keyName,60EXEC为什么要在上面使用MULTI,这是因为如果在执行INCRATE后未设置过期时间。限制:$userId,密钥将始终存在,因此您需要添加一个事务。

为什么限制访问频率

做服务接口时通常需要用到请求频率限制 Rate limiting,例如限制一个用户1分钟内最多可以范围100次

主要用来保证服务性能和保护数据安全

因为如果不进行限制,服务调用者可以随意访问,想调几次就调几次,会给服务造成很大的压力,降低性能,再比如有的接口需要验证调用者身份,如果不进行访问限制,调用者可以进行暴力尝试

redis如何解决“单位时间内只能n次操作”这样的问题?

假定要限制每分钟每个用户最多只能访问100个页面。

方案一:string
通过为用户使用一个名为 rate.limiting:userId 的字符串类型键,每次访问都使用 INCR命令递增该键的键值。
如果递增后的值为 1(第一次访问),则要为键设置过期时间 60秒。
这样每次用户访问都读取该键值,当键值超过100时,说明访问频率超过了限制,需要稍后访问。
该键过期后会自动删除,所以下一分钟用户访问次数又会重新计算。

伪代码如下:
    $isKeyExists = EXISTS rate.limiting:$userId    // 存在返回 1,不存在返回 0
    if $isKeyExists is 1
        $times = INCR rate.limiting:$userId
        if $times > 100        // 第100次访问会增加到101
            print 访问频率超过限制,请稍后再试 
            exit
    else
       MULTI       //此处,如果不加事务,竞态条件可能出现
    INCR rate.limiting:$userId
    EXPIRE
$keyName, 60
EXEC

上面为什么要用MULTI,那是因为如果在执行完INCR rate.limiting:$userId之后,如果(出现故障)没有设置过期时间,那么该键将永远存在,所以需要加上事务。

方案二:list

事实上,方案一有个问题。如果一个用户在第一分钟的最后一秒访问了99次,在下一分钟的第一秒访问了100次,相当于在两秒访问了199次,
与一分钟内最多只能访问100次相比还是差距比较大,尽管这种情况比较极端,但是依然存在。如果要实现粒度更小的控制方式,精确的保证每分钟最多访问100次,就需要使用第二种方案。

第二种方案需要记录用户每次的访问时间,因此对于每个用户,用列表类型的键记录他最近100次访问的时间。
如果键中的元素超过100个,就判断时间最早的元素距离现在的时间是否小于1分钟,如果是,则表示用户最近1分钟的访问次数超过100次,如果不是就将当前时间加入列表中,同时把最早的元素删除

伪代码如下:
    $limitLength = LLEN rate.limiting:$userId
    if $limitLength < 100
        LPUSH rate.limiting:$userId, now()
    else 
        $time = LINDEX rate.limiting:$userId, -1   // 取最后一个元素
        if now() - $time < 60
            print 访问频率超过限制,请稍后再试
        else
            LPUSH rate.limiting:$userId, now()
            LTRIM rate.limiting:$userId, 0, 99     // 删除[0~99]以外的元素

这种方式 now() 的功能是获得当前的 Unix时间,由于要记录当前访问时间,所以当要限制 “A时间最多访问B次” 时,如果”B”比较大,会占用较多内存,
实际使用时要去权衡。而且这种方法会出现就竞态条件,可以通过脚本避免。

但是在高并发的缓存系统中,大量使用事务是非常糟糕的,可以用redis自带的lua脚本功能实现多个操作的“原子性”

方案三:使用lua脚本实现频率限制

思路

把限制逻辑封装到一个Lua脚本中,调用时只需传入:key、限制数量、过期时间,调用结果就会指明是否运行访问

Redis实现访问控制频率第1张

local notexists = redis.call("set", KEYS[1], 1, "NX", "EX", tonumber(ARGV[2]))
if (notexists) then
  return 1
end
local current = tonumber(redis.call("get", KEYS[1]))
if (current == nil) then
  local result = redis.call("incr", KEYS[1])
  redis.call("expire", KEYS[1], tonumber(ARGV[2]))
  return result
end
if (current >= tonumber(ARGV[1])) then
  error("too many requests")
end
local result = redis.call("incr", KEYS[1])
return result

使用 eval 调用

eval 脚本 1 key 参数-允许的最大次数 参数-过期时间

免责声明:文章转载自《Redis实现访问控制频率》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇python 抓取cisco交换机配置文件FastAPI 学习之路(二十六)全局依赖项下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

开源netcore前后端分离,前端服务端渲染方案

SPA单页面应用容器开源地址:https://github.com/yuzd/Spa 功能介绍 前端应用开发完后打包后自助上传部署发布 配合服务端脚本(javascript)实现服务端业务逻辑编写渲染SSR功能 可以快速回滚到上一个版本 可以设置环境变量供SSR功能使用 服务端脚本提供执行日志 redis db三大组件打造强大的基于js的ssr服务端...

jedis 连接 redis

一、连接单机版的 redis /** * 直接连接 redis * @throws Exception */ @Test public void test1() throws Exception { //创建一个 jedis 对象,参数:host、post Jedis jedis = new Jedis("192.168.25.128...

幂等设计

最近做的项目的性能调优中关于幂等设计的一些总结 场景:假设有这样一个方法,包含了一些DB操作,check if existing then update else save. 如果两个线程同时去执行这个方法,并且他们处理的是同一条数据,期望应该是其中一个线程是save,另外一个是update。但是有可能线程的处理时间相当重合,线程A在check的时候,线程...

redis环境搭建(Linux)、Jredis

简介 1. NoSql是以key-value形式存储,和传统的关系型数据库不一样,不一定遵循传统数据库的一些基本要求,比如说遵循SQL标准,ACID属性,表结构等等,这类数据库主要有一下特点:非关系型的,分布式的,开源的,水平可扩展的。2. NoSql的特点:a) 处理超大量的数据。b) 运行在便宜的pc服务器集群上c) 击碎了性能瓶颈。3. NoSql适...

redis集群之REDIS CLUSTER

1. Linux系统配置 1.1. vm.overcommit_memory设置 overcommit_memory文件指定了内核针对内存分配的策略,其值可以是0、1、2。 0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。 1, 表示内核允许分配所有的物理内存,而不管...

《Skyline 监控系统工作原理分析》

Skyline 监控系统工作原理分析   Skyline 是一个实时的异常监测系统,它被动地接收 metrics 数据,并使用一系列算法自动地判断 metrics 是否异常,此外,用户可以很容易地根据自己应用数据的特点,提供自己的异常检测算法。 概述 Skyline 是一个实时的异常监测系统,它被动地接收 metrics 数据,并使用一系列算法自动地判断...