为了账号安全,请及时绑定邮箱和手机立即绑定
首页 手记 Redis限流实现

Redis限流实现

2018.10.12 00:42 7772浏览

令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.

令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量。


令牌桶内令牌生成借鉴Guava-RateLimiter类的设计 ,每次getToken根据时间戳生成token,不超过最大值。

核心参数:

last_mill_second 最后时间毫秒
curr_permits 当前可用的令牌
max_burst 令牌桶最大值
rate 每秒生成几个令牌
app 应用名称

local ratelimit_info=redis.pcall("HMGET",KEYS[1],"last_mill_second","curr_permits","max_burst","rate","app") 
local last_mill_second=ratelimit_info[1] 
local curr_permits=tonumber(ratelimit_info[2]) 
local max_burst=tonumber(ratelimit_info[3]) 
local rate=tonumber(ratelimit_info[4]) 
local app=tostring(ratelimit_info[5]) 
if app == nil then 
    return 0
end
local local_curr_permits=max_burst; 

if(type(last_mill_second) ~='boolean' and last_mill_second ~=nil) then 
    local reverse_permits=math.floor((ARGV[2]-last_mill_second)/1000)*rate 
    if(reverse_permits>0) then 
        redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[2]) 
    end 
    
    local expect_curr_permits=reverse_permits+curr_permits 
    local_curr_permits=math.min(expect_curr_permits,max_burst); 
else 
    redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[2])
end

local result=-1 
if(local_curr_permits-ARGV[1]>0) then 
    result=1 
    redis.pcall("HMSET",KEYS[1],"curr_permits",local_curr_permits-ARGV[1]) 
else redis.pcall("HMSET",KEYS[1],"curr_permits",local_curr_permits) 
end 

return result

Lua脚本在Redis中运行,保证了取令牌和生成令牌两个操作的原子性

springboot配置文件:

# REDIS (RedisProperties) 
# Redis数据库索引(默认为0) 
spring.redis.database=0
# Redis服务器地址 
spring.redis.host=127.0.0.1
# Redis服务器连接端口 
spring.redis.port=6379 
# Redis服务器连接密码(默认为空) 
spring.redis.password= 
# 连接池最大连接数(使用负值表示没有限制) 
spring.redis.jedis.pool.max-active=8 
# 连接池最大阻塞等待时间(使用负值表示没有限制) 
spring.redis.jedis.pool.max-wait=-1 
# 连接池中的最大空闲连接 
spring.redis.jedis.pool.max-idle=8 
# 连接池中的最小空闲连接 
spring.redis.jedis.pool.min-idle=0 
# 连接超时时间(毫秒) 
spring.redis.timeout=2000

java 配置类:

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean("ratelimitLua")
    public DefaultRedisScript getRedisScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("ratelimit.lua"));
        redisScript.setResultType(java.lang.Long.class);
        return redisScript;
    }

    @Bean("ratelimitInitLua")
    public DefaultRedisScript getInitRedisScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("ratelimitInit.lua"));
        redisScript.setResultType(java.lang.Long.class);
        return redisScript;
    }
}

public class Constants {
    public static final String RATE_LIMIT_KEY = "ratelimit:";
}

public enum Token {
    SUCCESS, FAILED;
    public boolean isSuccess() {
        return this.equals(SUCCESS);
    }

    public boolean isFailed() {
        return this.equals(FAILED);
    }
}

限流客户端:

@Service
public class RateLimitClient {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Qualifier("getRedisScript")
    @Resource
    RedisScript<Long> ratelimitLua;
    @Qualifier("getInitRedisScript")
    @Resource
    RedisScript<Long> ratelimitInitLua;

    public Token initToken(String key) {
        Token token = Token.SUCCESS;
        Long currMillSecond = stringRedisTemplate
                .execute((RedisCallback<Long>) redisConnection -> redisConnection.time());
        /**
         * redis.pcall("HMSET",KEYS[1], "last_mill_second",ARGV[1],
         * "curr_permits",ARGV[2], "max_burst",ARGV[3], "rate",ARGV[4], "app",ARGV[5])
         */
        Long accquire = stringRedisTemplate.execute(ratelimitInitLua, Collections.singletonList(getKey(key)),
                currMillSecond.toString(), "1", "10", "10", "skynet");
        if (accquire == 1) {
            token = Token.SUCCESS;
        } else if (accquire == 0) {
            token = Token.SUCCESS;
        } else {
            token = Token.FAILED;
        }
        return token;
    }

    /**
     * 获得key操作
     *
     * @param key
     * @return
     */
    public Token accquireToken(String key) {
        return accquireToken(key, 1);
    }

    public Token accquireToken(String key, Integer permits) {
        Token token = Token.SUCCESS;
        Long currMillSecond = stringRedisTemplate
                .execute((RedisCallback<Long>) redisConnection -> redisConnection.time());
        Long accquire = stringRedisTemplate.execute(ratelimitLua, Collections.singletonList(getKey(key)),
                permits.toString(), currMillSecond.toString());
        if (accquire == 1) {
            token = Token.SUCCESS;
        } else {
            token = Token.FAILED;
        }
        return token;
    }

    public String getKey(String key) {
        return Constants.RATE_LIMIT_KEY + key;
    }
}

lua脚本:

local result=1 
redis.pcall("HMSET",KEYS[1], "last_mill_second",ARGV[1], "curr_permits",ARGV[2], "max_burst",ARGV[3], "rate",ARGV[4], "app",ARGV[5]) 
return result

原文地址:https://blog.csdn.net/sunlihuo/article/details/79700225?utm_source=copy


点击查看更多内容
12人点赞

若觉得本文不错,就分享一下吧!

评论

相关文章推荐

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消