您当前的位置:首页 > 电脑百科 > 数据库 > Redis

精讲Redis限流:多种方法与生产实践

时间:2023-09-06 12:54:43  来源:今日头条  作者:写代码那些事

限流是一种重要的应用场景,用于控制访问速率以防止服务器过载或滥用。redis可以用于实现多种限流算法,如令牌桶、漏桶等。

令牌桶算法实现限流

令牌桶算法是一种常见的限流算法,它通过维护一个固定容量的令牌桶来控制流量。每个请求需要获取一个令牌,如果桶中没有足够的令牌,则请求会被限制。

首先,你需要在Redis中设置一个计数器和一个定时器来模拟令牌桶:

import redis
import time

# 连接Redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 设置令牌桶容量和每秒生成的令牌数
bucket_capacity = 10
tokens_per_second = 2

# 初始化令牌桶
r.set('tokens', bucket_capacity)
r.set('last_time', int(time.time()))

# 请求令牌的函数
def request_token():
    current_time = int(time.time())
    last_time = int(r.get('last_time'))
    elapsed_time = current_time - last_time

    # 计算新增的令牌数量
    new_tokens = elapsed_time * tokens_per_second
    current_tokens = int(r.get('tokens'))

    # 更新令牌数量
    if new_tokens + current_tokens > bucket_capacity:
        r.set('tokens', bucket_capacity)
    else:
        r.set('tokens', new_tokens + current_tokens)

    r.set('last_time', current_time)

# 使用令牌的代码
def process_request():
    if int(r.get('tokens')) > 0:
        # 执行你的请求处理逻辑
        print('请求通过')
        r.decr('tokens')  # 消耗一个令牌
    else:
        print('请求被限制')

# 测试请求
for _ in range(15):
    request_token()
    process_request()
    time.sleep(1)

这个示例中,我们通过Redis来维护令牌桶的状态,并在请求到来时检查是否有足够的令牌。如果有足够的令牌,请求将被处理,否则请求将被限制。

漏桶算法实现限流

漏桶算法是另一种流量控制算法,它维护一个固定容量的漏桶,请求进来后,会以固定速率从漏桶中排出。

以下是使用Redis实现漏桶算法的示例:

import redis
import time

# 连接Redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 设置漏桶容量和漏出速率(每秒排出的请求数)
bucket_capacity = 10
leak_rate = 2

# 初始化漏桶
r.set('bucket_capacity', bucket_capacity)
r.set('last_leak_time', int(time.time()))

# 请求处理函数
def process_request():
    current_time = int(time.time())
    last_leak_time = int(r.get('last_leak_time'))
    time_elapsed = current_time - last_leak_time

    # 计算漏出的请求数
    leaked_requests = min(int(r.get('bucket_capacity')) * (time_elapsed // 1), int(r.get('bucket_capacity')))

    # 更新漏桶状态
    r.incrby('bucket_capacity', leaked_requests)
    r.set('last_leak_time', current_time)

    # 处理请求
    if int(r.get('bucket_capacity')) >= 1:
        print('请求通过')
        r.decr('bucket_capacity')
    else:
        print('请求被限制')

# 测试请求
for _ in range(15):
    process_request()
    time.sleep(1)

在漏桶算法中,请求会被排入漏桶中,然后以固定速率漏出。如果漏桶中有请求,则请求会被处理,否则请求会被限制。

以上两个案例虽然能够实现限流,但是存在一定的问题,无法满足生产的要求,下面讲一下其他思路

有序集合zset实现限流

使用Redis的有序集合(ZSET)也可以实现限流功能。有序集合中的成员可以关联一个分数,我们可以使用分数来表示每个请求的权重或时间戳,并利用有序集合的排序特性来判断请求是否被允许。

以下是使用有序集合实现基于时间窗口的限流示例:

import redis
import time

# 连接Redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 限流配置
max_requests = 10  # 在时间窗口内允许的最大请求数
window_duration = 60  # 时间窗口的持续时间(秒)

# 请求处理函数
def process_request(user_id):
    current_time = time.time()
    zset_key = "requests:" + user_id
    # 删除时间窗口之外的请求记录
    r.zremrangebyscore(zset_key, '-inf', current_time - window_duration)
    
    # 获取当前时间窗口内的请求数
    requests_in_window = r.zcard(zset_key)
    
    if requests_in_window < max_requests:
        # 如果请求数在限制范围内,允许请求并记录请求时间
        r.zadd(zset_key, {str(current_time): current_time})
        print('请求通过')
    else:
        print('请求被限制')

# 测试请求
user_id = "user123"
for _ in range(15):
    process_request(user_id)
    time.sleep(2)

在这个示例中,我们为每个用户维护一个有序集合,其中成员是请求的时间戳,分数也设置为时间戳。在处理请求时,我们首先删除时间窗口之外的请求记录,然后检查时间窗口内的请求数是否超过了限制。如果没有超过限制,允许请求并记录请求时间戳。

这种方法可以实现基于时间窗口的限流,你可以根据需要调整max_requests和window_duration来配置限流策略。

但是这样又引发了一个并发性问题

在分布式系统中,处理请求的并发性是一个重要考虑因素,特别是在多个客户端同时发送请求的情况下。以下是一些常见的方法来确保process_request操作的并发安全性:

互斥锁(Mutex Lock):使用互斥锁可以确保在同一时刻只有一个线程或进程可以执行process_request操作。这可以通过在关键部分的代码周围放置锁来实现。在Redis中,你可以使用Redis的SETNX(Set If Not Exists)命令来实现互斥锁,确保只有一个客户端可以获取锁并执行请求处理操作。

def process_request(user_id):
    lock_key = "lock:" + user_id
    acquired_lock = r.setnx(lock_key, "1")

    if acquired_lock:
        try:
            # 在获取锁后,执行请求处理操作
            current_time = time.time()
            zset_key = "requests:" + user_id
            # 删除时间窗口之外的请求记录
            r.zremrangebyscore(zset_key, '-inf', current_time - window_duration)
            
            # 获取当前时间窗口内的请求数
            requests_in_window = r.zcard(zset_key)
            
            if requests_in_window < max_requests:
                # 如果请求数在限制范围内,允许请求并记录请求时间
                r.zadd(zset_key, {str(current_time): current_time})
                print('请求通过')
            else:
                print('请求被限制')
        finally:
            # 释放锁
            r.delete(lock_key)
    else:
        print('无法获取锁,请求被限制')

分布式锁:如果你的系统是分布式的,你可以考虑使用分布式锁来确保不同节点上的请求处理代码不会同时执行。一些常见的分布式锁实现包括基于ZooKeeper或Redis的分布式锁。这些锁可以协调不同节点之间的并发执行。

事务:Redis支持事务,你可以使用MULTI和EXEC命令将多个操作包装在一个事务中。在这种情况下,Redis会确保整个事务要么全部成功执行,要么全部失败,从而保证一致性。

这种虽然能解决问题,但是并不是最优解

EXEC + lua 实现

使用Redis的EXEC命令和Lua脚本可以确保多个Redis命令在一个事务中执行,从而保证一致性。下面是一个使用EXEC和Lua脚本来实现请求处理的示例:

import redis

# 连接Redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 限流配置
max_requests = 10  # 在时间窗口内允许的最大请求数
window_duration = 60  # 时间窗口的持续时间(秒)

# Lua脚本,用于限流处理
lua_script = """
local user_id = KEYS[1]
local max_requests = tonumber(ARGV[1])
local window_duration = tonumber(ARGV[2])
local current_time = tonumber(ARGV[3])

-- 删除时间窗口之外的请求记录
redis.call('ZREMRANGEBYSCORE', 'requests:'..user_id, '-inf', current_time - window_duration)

-- 获取当前时间窗口内的请求数
local requests_in_window = redis.call('ZCARD', 'requests:'..user_id)

if requests_in_window < max_requests then
    -- 如果请求数在限制范围内,允许请求并记录请求时间
    redis.call('ZADD', 'requests:'..user_id, current_time, current_time)
    return 'ALLOWED'
else
    return 'LIMITED'
end
"""

# 请求处理函数
def process_request(user_id):
    current_time = int(time.time())
    result = r.eval(lua_script, 1, user_id, max_requests, window_duration, current_time)
    
    if result == b'ALLOWED':
        print('请求通过')
    else:
        print('请求被限制')

# 测试请求
user_id = "user123"
for _ in range(15):
    process_request(user_id)
    time.sleep(2)

在上述示例中,我们使用Lua脚本编写了一个与之前的请求处理逻辑相同的限流处理逻辑。然后,我们通过eval命令将Lua脚本传递给Redis,并在一个事务中执行它。这样可以确保在同一事务内执行多个Redis命令,从而保证了一致性。

请注意,在Lua脚本中,我们使用了Redis的命令来执行限流逻辑,然后根据结果返回相应的值,以便在Python/ target=_blank class=infotextkey>Python中进行处理。如果请求被限制,Lua脚本返回'LIMITED',否则返回'ALLOWED'。

通过这种方式,你可以使用Redis实现基于令牌桶算法的限流功能。可以根据需要调整令牌桶容量和生成速率来满足你的应用需求。此外,需要注意在高并发情况下,需要谨慎处理并发问题



Tags:Redis   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  Search: Redis  点击:(11)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25  Search: Redis  点击:(9)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  Search: Redis  点击:(17)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  Search: Redis  点击:(8)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  Search: Redis  点击:(47)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  Search: Redis  点击:(39)  评论:(0)  加入收藏
工作中Redis有哪些好用的运维工具
工作中使用 Redis 时,如果大家公司没有专业运维,可能开发人员就会面临这些运维的工作,包括 Redis 的运行状态监控,数据迁移,主从集群、切片集群的部署和运维等等。本文我就从这三...【详细内容】
2024-02-06  Search: Redis  点击:(55)  评论:(0)  加入收藏
深入Go底层原理,重写Redis中间件实战
Go语言以其简洁、高效和并发性能而闻名,深入了解其底层原理可以帮助我们更好地利用其优势。在本文中,我们将探讨如何深入Go底层原理,以及如何利用这些知识重新实现一个简单的Re...【详细内容】
2024-01-25  Search: Redis  点击:(66)  评论:(0)  加入收藏
批量执行Redis命令的四种方式!
前言在我们的印象中Redis命令好像都是一个个单条进行执行的,如果有人问你如何批量执行Redis命令,你能回答的上吗,或者说能答出几种方式呢?最容易想到的是Redis的一些批量命令,例...【详细内容】
2024-01-17  Search: Redis  点击:(57)  评论:(0)  加入收藏
Redis 实现多规则限流的思考与实践
市面上很多介绍redis如何实现限流的,但是大部分都有一个缺点,就是只能实现单一的限流,比如1分钟访问1次或者60分钟访问10次这种,但是如果想一个接口两种规则都需要满足呢,我们的...【详细内容】
2024-01-03  Search: Redis  点击:(109)  评论:(0)  加入收藏
▌简易百科推荐
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  dbaplus社群    Tags:Redis   点击:(11)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25    51CTO  Tags:Redis   点击:(9)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  后端Q  微信公众号  Tags:Redis   点击:(17)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  OSC开源社区    Tags:Redis   点击:(8)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  编程技术汇    Tags:Redis   点击:(47)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  楼仔  微信公众号  Tags:Redis   点击:(39)  评论:(0)  加入收藏
工作中Redis有哪些好用的运维工具
工作中使用 Redis 时,如果大家公司没有专业运维,可能开发人员就会面临这些运维的工作,包括 Redis 的运行状态监控,数据迁移,主从集群、切片集群的部署和运维等等。本文我就从这三...【详细内容】
2024-02-06  waynaqua    Tags:Redis   点击:(55)  评论:(0)  加入收藏
批量执行Redis命令的四种方式!
前言在我们的印象中Redis命令好像都是一个个单条进行执行的,如果有人问你如何批量执行Redis命令,你能回答的上吗,或者说能答出几种方式呢?最容易想到的是Redis的一些批量命令,例...【详细内容】
2024-01-17  小许code  微信公众号  Tags:Redis命令   点击:(57)  评论:(0)  加入收藏
Redis 实现多规则限流的思考与实践
市面上很多介绍redis如何实现限流的,但是大部分都有一个缺点,就是只能实现单一的限流,比如1分钟访问1次或者60分钟访问10次这种,但是如果想一个接口两种规则都需要满足呢,我们的...【详细内容】
2024-01-03  架构精进之路  微信公众号  Tags:Redis   点击:(109)  评论:(0)  加入收藏
一站式Redis解决方案
Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常根据数据的key查询其value值,Redis没有模糊条件查询,在面对一些需...【详细内容】
2024-01-01  大雷家吃饭    Tags:Redis   点击:(66)  评论:(0)  加入收藏
站内最新
站内热门
站内头条