Redis+Lua脚本实现分布式限流

平时项目中做分布式限流可能会使用一些框架,如Sentinel,Hystrix等开源框架.今天介绍一种自己开发的分布式限流方案.使用Redis+Lua的方式.

简介

平时研发中可能存在这种场景.调用外部API接口时由于外部接口存在限频,如阿里云短信发送短信接口每秒只允许调用100次,超过这个频次后请求被拒绝.对于这种场景不清楚Sentinel和Hystrix等是否可以使用.那么为了保证短信消息的不丢失,短信系统需要针对外部接口的限频做一些消息可靠性保证.本文已短信消息为原型进行介绍分布式限流的方案.

方案

Redis+Lua+MQ实现限流的同时保证消息不丢失与及时发送
Redis+Lua脚本实现分布式限流_第1张图片
1.首先使用Redis的list存储消息的消息到来时刻的时间戳
2.redis+lua保证分布式系统中操作的原子性
3.消息队列(RocketMQ)的延迟性保证消息不丢失与及时发送
首先定义几个变量:
限频数量阀值:100
限频时间阀值:1min
即1min只允许发送100条消息
下面我们看看一条消息是如何发送出去的.

  • 首先一条消息来到了我们的限流器,看list当中是否存在100条数据,如果没有达到100条数据即可立即发送该消息.那么将当前消息的时间戳存储到list当中.
  • 如果list当中已经存在100条数据了,那么需要进一步判断当前消息的时间戳与list头的时间戳的差值t是否大于1min,如果大于1min说明可以进行下一分钟的消息发送了,那么将list头中的时间戳出队.将当前消息时间戳入队
  • 如果当前时间戳与list队头时间戳差值t不足1min,那么将当前消息发送到RocketMQ的延迟topic中,延迟时间为1min-t.延迟时间到达时即可发送.

Redis+lua

local function addToQueue(x,time)
     local count=0
     for i=1,x,1 do
         redis.call('lpush',KEYS[1],time)
         count=count+1
     end
     return count
 end
 local result=0
 local timeBase = redis.call('lindex',KEYS[1], tonumber(ARGV[2])-tonumber(ARGV[1]))
 if (timeBase == false) or (tonumber(ARGV[4]) - tonumber(timeBase)>tonumber(ARGV[3])) then
   result=result+addToQueue(tonumber(ARGV[1]),tonumber(ARGV[4]))
 end
 if (timeBase~=false) then
    redis.call('ltrim',KEYS[1],0,tonumber(ARGV[2])-1)
 end
 return result

如何在代码中使用lua脚本

public boolean acquirePromise(String redisKey,long count, long rateCount, long rateTime) {
        validate(redisKey, count, rateCount, rateTime);
        String luaScript = ScriptUtil.getScript("rateLimit.lua");
        RedisScript redisScript = new DefaultRedisScript<>(luaScript, Number.class);
        Long currMillSecond = redisTemplate.execute((RedisCallback) redisConnection -> redisConnection.time());
        Number result = redisTemplate.execute(redisScript, Collections.singletonList(redisKey), String.valueOf(count), String.valueOf(rateCount), String.valueOf(rateTime), String.valueOf(currMillSecond));
        if(result != null && result.intValue() > 0) {
            //放行
            return true;
        }
        //限流
        return false;
    }

Tips:
1.如果使用RocketMQ的延迟Topic需要使用阿里云版本的RocketMQ,因为开源的RocketMQ不支持毫秒级别的延迟
2.redis 4.0 以后开始支持扩展模块,redis-cell是一个用rust语言编写的基于令牌桶算法的的限流模块,提供原子性的限流功能,并允许突发流量.

你可能感兴趣的:(java,spring,boot,redis)