美文网首页
基于redis的分布式限流方案

基于redis的分布式限流方案

作者: java高并发 | 来源:发表于2019-02-21 17:43 被阅读44次

    为什么要选用redis:

    redis效率高,易扩展
    redis对语言无关,可以更好的接入不同语言开发的系统(异构)
    redis单进程单线程的特点可以更好的解决最终一致性,多进程间协同控制更为容易

    计数器算法实现

    先来看一个比较简单的计数器算法的实现。

    计数器算法前文限流技术总结有过详细说明,现在我们用lua代码来实现这个算法:

    令牌桶算法

    计数器算法容易出现不平滑的情况,瞬间的qps有可能超过系统的承载。因此在实际场景中我们一般很少使用。

    令牌桶算法是一个比较常用的限流算法,也是Guava中使用的算法。我们使用lua来实现令牌桶算法:

    -- key
    local key = KEYS[1]
    -- 最大存储的令牌数
    local max_permits = tonumber(KEYS[2])
    -- 每秒钟产生的令牌数
    local permits_per_second = tonumber(KEYS[3])
    -- 请求的令牌数
    local required_permits = tonumber(ARGV[1])
    
    -- 下次请求可以获取令牌的起始时间
    local next_free_ticket_micros = tonumber(redis.call('hget', key, 'next_free_ticket_micros') or 0)
    
    -- 当前时间
    local time = redis.call('time')
    local now_micros = tonumber(time[1]) * 1000000 + tonumber(time[2])
    
    -- 查询获取令牌是否超时
    if (ARGV[2] ~= nil) then
        -- 获取令牌的超时时间
        local timeout_micros = tonumber(ARGV[2])
        local micros_to_wait = next_free_ticket_micros - now_micros
        if (micros_to_wait > timeout_micros) then
            return micros_to_wait
        end
    end
    
    -- 当前存储的令牌数
    local stored_permits = tonumber(redis.call('hget', key, 'stored_permits') or 0)
    -- 添加令牌的时间间隔
    local stable_interval_micros = 1000000 / permits_per_second
    
    -- 补充令牌
    if (now_micros > next_free_ticket_micros) then
        local new_permits = (now_micros - next_free_ticket_micros) / stable_interval_micros
        stored_permits = math.min(max_permits, stored_permits + new_permits)
        next_free_ticket_micros = now_micros
    end
    
    -- 消耗令牌
    local moment_available = next_free_ticket_micros
    local stored_permits_to_spend = math.min(required_permits, stored_permits)
    local fresh_permits = required_permits - stored_permits_to_spend;
    local wait_micros = fresh_permits * stable_interval_micros
    
    redis.replicate_commands()
    redis.call('hset', key, 'stored_permits', stored_permits - stored_permits_to_spend)
    redis.call('hset', key, 'next_free_ticket_micros', next_free_ticket_micros + wait_micros)
    redis.call('expire', key, 10)
    
    -- 返回需要等待的时间长度
    return moment_available - now_micros
    

    如果是acquire方法,则执行:

    eval 'lua脚本' 3 '自定义的key' '最大存储的令牌数' '每秒钟产生的令牌数' '请求的令牌数'
    

    redis将返回获取请求成功后,线程需要等待的微秒数

    如果是tryAcquire方法,则执行:

    eval 'lua脚本' 3 '自定义的key' '最大存储的令牌数' '每秒钟产生的令牌数' '请求的令牌数' '最大等待的微秒数'
    

    redis同样返回需要等待的微秒数,将该返回值与最大等待微秒数做比较,如果redis返回的值较大,则说明失败;反之则是成功,并根据返回值让线程等待。

    end

    顺便在此给大家推荐一个Java方面的交流学习群:957734884,里面会分享一些高级面试题,还有资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,主要针对Java开发人员提升自己,突破瓶颈,相信你来学习,会有提升和收获。在这个群里会有你需要的内容 朋友们请抓紧时间加入进来吧


    加群领取资料 加群领取免费的Java视频资料

    相关文章

      网友评论

          本文标题:基于redis的分布式限流方案

          本文链接:https://www.haomeiwen.com/subject/ibjdyqtx.html