redis+Lua API限流

     之前参与过sendcloud API接口的开发,开放给用户的API接口,在用户恶意调用或者数据容量增长过快,接口查询造成系统cpu负载过大,数据库宕机的事故,为了避免这种致命事故出现,我们API需要保护,需要对API进行限流策略。下面我们以接口:http://api2.sendcloud.net/api/data/emailStatus

一、redis实现

  
private boolean overRateLimit(rateLimitKey ) {
         int maxValue = 5000;
        if (RedisUtil.exists(rateLimitKey)) {
            Long total = RedisUtil.incr(rateLimitKey, 1);
            if (total == null) {
                return false;
            }
            if (total > maxValue) {

                //下面RedisUtil.ttl(rateLimitKey)==-1 片段解决下面2个问题,非常重要

                //1.如果客户端产生了一个相同的,不过期的rateLimitKey  key该rateLimitKey  会一直计数,造成该接口拉黑,不可用

                //2.redis主从切换,导致过期数据没有同步丢失,也会产生一个相同的,不过期的rateLimitKey  key,造成接口不可用

                if(RedisUtil.ttl(rateLimitKey)==-1){
                    RedisUtil.del(rateLimitKey);
                }
                 return true;
            }
        } else {
            RedisUtil.setnx(rateLimitKey, "1", RedisUtil.DEFAULT_EXPIRE);

           }

        return false;

    }

 

二、lua+redis实现

    redis+lua 有几个有点

  •       减少网络开销:本来N次网络请求的操作,可以用一个请求完成。
  •       原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。

(1)创建status.lua 脚本

----设置KEY=rateLimitKey
---每次请求给KEY=rateLimitKey 计数+1 设置过期时间为1秒钟
---如果KEY=rateLimitKey 计数>2000 触发限流操作
---KEYS[1]
---ARGV
---ARGV[2] 2000
local visitTimes=redis.call('INRC',KEYS[1])
if (visitTimes==1) then
      redis.call('expire',KEYS[1],ARGV[1])
end
if( visitTimes>ARGV[2] )  then
      local ttl_time=redis.call('ttl',KEYS[1])

      if ( tonumber(ttl_time)==-1 )  then
           redis.call('del',KEYS[1])
      end
      return true
else
      return false
end

(2)配置bean(springboot框架)

 

@Configuration
public class LuaConfiguration {
    @Bean
    public DefaultRedisScript<Boolean> statusRedisScript() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("status.lua")));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }
}

(3)调用

@RestController
public class EmailStatusController {
    @Resource
    private DefaultRedisScript<Boolean> statusRedisScript;
    @Resource
    private PRedisTemplate pRedisTemplate;

    @GetMapping("/status")
    public ResponseEntity status() {
        		boolean 
            result=pRedisTemplate.getRedisTemplateString().execute(statusRedisScript, 
            Collections.singletonList("rateLimitKey"), 60,2000);
            return ResponseEntity.ok(execute);
            ..............


    }
}

 

来自:https://web.sendcloud.net

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现分布式限流可以使用 RedisLua 脚本来完成。以下是可能的实现方案: 1. 使用 Redis 的 SETNX 命令来实现基于令牌桶算法的限流 令牌桶算法是一种常见的限流算法,它可以通过令牌的放置和消耗来控制流量。在 Redis 中,我们可以使用 SETNX 命令来实现令牌桶算法。 具体实现步骤如下: - 在 Redis 中创建一个有序集合,用于存储令牌桶的令牌数量和时间戳。 - 每当一个请求到达时,我们首先获取当前令牌桶中的令牌数量和时间戳。 - 如果当前时间戳与最后一次请求的时间戳之差大于等于令牌桶中每个令牌的发放时间间隔,则将当前时间戳更新为最后一次请求的时间戳,并且将令牌桶中的令牌数量增加相应的数量,同时不超过最大容量。 - 如果当前令牌桶中的令牌数量大于等于请求需要的令牌数量,则返回 true 表示通过限流,将令牌桶中的令牌数量减去请求需要的令牌数量。 - 如果令牌桶中的令牌数量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现令牌桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 令牌桶的容量 local capacity = tonumber(ARGV[1]) -- 令牌的发放速率 local rate = tonumber(ARGV[2]) -- 请求需要的令牌数量 local tokens = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取当前令牌桶中的令牌数量和时间戳 local bucket = redis.call('ZREVRANGEBYSCORE', key, now, 0, 'WITHSCORES', 'LIMIT', 0, 1) -- 如果令牌桶为空,则初始化令牌桶 if not bucket[1] then redis.call('ZADD', key, now, capacity - tokens) return 1 end -- 计算当前令牌桶中的令牌数量和时间戳 local last = tonumber(bucket[2]) local tokensInBucket = tonumber(bucket[1]) -- 计算时间间隔和新的令牌数量 local timePassed = now - last local newTokens = math.floor(timePassed * rate) -- 更新令牌桶 if newTokens > 0 then tokensInBucket = math.min(tokensInBucket + newTokens, capacity) redis.call('ZADD', key, now, tokensInBucket) end -- 检查令牌数量是否足够 if tokensInBucket >= tokens then redis.call('ZREM', key, bucket[1]) return 1 else return 0 end ``` 2. 使用 RedisLua 脚本实现基于漏桶算法的限流 漏桶算法是另一种常见的限流算法,它可以通过漏桶的容量和漏水速度来控制流量。在 Redis 中,我们可以使用 Lua 脚本实现漏桶算法。 具体实现步骤如下: - 在 Redis 中创建一个键值对,用于存储漏桶的容量和最后一次请求的时间戳。 - 每当一个请求到达时,我们首先获取当前漏桶的容量和最后一次请求的时间戳。 - 计算漏水速度和漏水的数量,将漏桶中的容量减去漏水的数量。 - 如果漏桶中的容量大于等于请求需要的容量,则返回 true 表示通过限流,将漏桶中的容量减去请求需要的容量。 - 如果漏桶中的容量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现漏桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 漏桶的容量 local capacity = tonumber(ARGV[1]) -- 漏水速度 local rate = tonumber(ARGV[2]) -- 请求需要的容量 local size = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取漏桶中的容量和最后一次请求的时间戳 local bucket = redis.call('HMGET', key, 'capacity', 'last') -- 如果漏桶为空,则初始化漏桶 if not bucket[1] then redis.call('HMSET', key, 'capacity', capacity, 'last', now) return 1 end -- 计算漏水的数量和漏桶中的容量 local last = tonumber(bucket[2]) local capacityInBucket = tonumber(bucket[1]) local leak = math.floor((now - last) * rate) -- 更新漏桶 capacityInBucket = math.min(capacity, capacityInBucket + leak) redis.call('HSET', key, 'capacity', capacityInBucket) redis.call('HSET', key, 'last', now) -- 检查容量是否足够 if capacityInBucket >= size then return 1 else return 0 end ``` 以上是使用 RedisLua 脚本实现分布式限流的两种方案,可以根据实际需求选择适合的方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值