百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

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

mhr18 2024-11-27 11:59 23 浏览 0 评论

知识星球:写代码那些事

如果你有收获|欢迎|点赞|关注|转发

这里会定期更新|大厂的开发|架构|方案设计

这里也会更新|如何摸鱼|抓虾

欢迎来到写代码那些事限流是一种重要的应用场景,用于控制访问速率以防止服务器过载或滥用。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中进行处理。如果请求被限制,Lua脚本返回'LIMITED',否则返回'ALLOWED'。

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

#程序员##职场##编程##python#

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: