限流功能的实现

简介: 限流功能的实现

前言


我有一个朋友, 最近用到了限流方式, 主要是限制前台用户请求接口次数, 那么就让我们来聊一聊最常见的限流方式吧


正文


假设限制10s最多请求二十次, redis_key为user_id:api

为了方便下面就直接用python实现了


固定窗口:


解释


即固定死10s时间段, 在这期间只接受二十次请求, 请求打满了就只能登下一段10s, 可以用incr实现,接受请求调用incr key, 如果value=1, 则证明为第一次请求, 使用exipre加上10s过期时间,如果大于20, 则拒绝该请求


缺点


有可能会遇到突刺情况, 即在9.9s和10.1s各发出20个请求, 这就相当于10s内发了40个了


实现
```
import redis
client = redis.Redis(host="127.0.0.1", port=6379)
num = client.incr("user_id:api")
if num == 1:
    client.expire(num, 10)
elif num > 20:
    print("请求超过次数")
```

滑动窗口:

解释


即时间是流动的, 一直保持者当前时间往后推10s的一个滑动窗口, 可以用zset实现接受请求调用zadd添加窗口数据, score为当前时间, value为一个唯一值(一般用毫秒时间戳), 然后删除(zremrangebyscore)当前时间10s前的数据, 在获取(zcard)当前窗口内的请求次数,进行判断, 记得也需要加上一个过期时间, 避免空间占用问题


实现:
```
import redis, time
client = redis.Redis(host="127.0.0.1", port=6379)
def is_action_allowed(key, period, max_count):
    now_ts = int(time.time() * 1000)  # 毫秒时间戳
    with client.pipeline() as pipe:  # client 是 StrictRedis 实例
        # 记录行为
        pipe.zadd(key, now_ts, now_ts)  # value 和 score 都使用毫秒时间戳
        # 移除时间窗口之前的行为记录,剩下的都是时间窗口内的
        pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
        # 获取窗口内的行为数量
        pipe.zcard(key)
        # 设置 zset 过期时间,避免冷用户持续占用内存
        # 过期时间应该等于时间窗口的长度,再多宽限 1s
        pipe.expire(key, period + 1)
        # 批量执行
        _, _, current_count, _ = pipe.execute()
    # 比较数量是否超标
    return current_count <= max_count
is_action_allowed("user_id:api", 10, 20)
```


漏桶算法:


解释


即一个水桶, 进水(接受请求)的速率不限, 出水(处理请求)的速率是一定的, 而且水桶大小也是有限制的, 也有可能造成水桶溢出


缺点


因为速率是一样的, 所以假如有突发大量请求的话就不是很合适了


实现
```
import time
class Funnel(object):
    def __init__(self, capacity, leaking_rate):
        self.capacity = capacity  # 漏斗容量
        self.leaking_rate = leaking_rate  # 漏嘴流水速率
        self.left_quota = capacity  # 漏斗剩余空间
        self.leaking_ts = time.time()  # 上一次漏水时间
    def make_space(self):
        now_ts = time.time()
        delta_ts = now_ts - self.leaking_ts  # 距离上一次漏水过去了多久
        delta_quota = delta_ts * self.leaking_rate  # 又可以腾出不少空间了
        if delta_quota < 1:  # 腾的空间太少,那就等下次吧
            return
        self.left_quota += delta_quota  # 增加剩余空间
        self.leaking_ts = now_ts  # 记录漏水时间
        if self.left_quota > self.capacity:  # 剩余空间不得高于容量
            self.left_quota = self.capacity
    def watering(self, quota):
        self.make_space()
        if self.left_quota >= quota:  # 判断剩余空间是否足够
            self.left_quota -= quota
            return True
        return False
funnels = {}  # 所有的漏斗
# capacity  漏斗容量
# leaking_rate 漏嘴流水速率 quota/s
def is_action_allowed(key, capacity, leaking_rate):
    funnel = funnels.get(key)
    if not funnel:
        funnel = Funnel(capacity, leaking_rate)
        funnels[key] = funnel
    return funnel.watering(1)
print(is_action_allowed("user_id:api", 15, 0.5))
```


令牌桶算法:


解释


即也是一个桶, 按照设定的速率往桶里放令牌, 10s二十次即1s放两个令牌(允许处理两次请求), 然后请求来之后必须从桶里取出来令牌才可以进行处理, 没有令牌则选择拒绝或等待


实现


利用redis-cell可以实现, 这块其实有一个问题, 都说的是redis-cell是用了漏桶算法实现的, 但是我用的时候其实是按照令牌桶的方式来用的(即我拿到令牌才可以去处理请求)…这块就比较尴尬了, 有大神知道的话可以帮我纠正一下


redis-cell 是redis的一个插件, 这里我用于测试直接用docker装一个

docker search redis-cell
docker pull hsz1273327/redis-cell
docker run -d -p 6379:6379 --name redis hsz1273327/redis-cell:latest

其实redis-cell就一个命令 cl.throttle , 这里还是用上面的例子举例


user_id:api 15 20 10 1
15 是桶的容量 -- 即同时能存在多少个令牌
20 就是速率限制了 
10 单位时间(s)
1  一次取出几个令牌, 默认是一


命令返回值


本地:0>cl.throttle user_id:api 15 20 10 1
 1)  "0"   // 0是允许, 1是拒绝
 2)  "16"  // 桶容量
 3)  "15"  // 剩余令牌
 4)  "-1"  // 拒绝的话需要等待多长时间再试(这就很贴心了)
 5)  "0"   // 多长时间令牌放满


目录
相关文章
|
算法 Python
Python算法——广度优先搜索
Python算法——广度优先搜索
910 0
|
数据采集 算法 JavaScript
彩票中奖率的真相:用 JavaScript 看透彩票背后的随机算法(上)
原本这篇文章是打算叫「假如我是彩票系统开发者」,但细想一下,如果在文章中引用太多的 JavaScript 的话,反而不是那么纯粹,毕竟也只是我的一厢情愿,彩票开发也不全如本文所讲,有所误导的话便也是得不偿失了。
|
5月前
|
人工智能 IDE API
百炼 Coding Plan 是什么?售罄抢不到怎么办?附抢购技巧及平替方案
阿里云百炼Coding Plan是高性价比AI编程订阅服务,聚合Qwen、GLM、Kimi等主流大模型,兼容Cursor等工具。Lite版已停售,Pro版每日9:30限量抢购。抢不到?可选按量付费——新用户赠100万Token,轻度使用月费仅10-50元,即开即用。
1155 1
|
9月前
|
JavaScript Java 数据库连接
中阶段 低代码平台芋道:代码本地运行(☆)
新人入职需拉取项目代码并本地运行,常见问题如JDK、Maven、IDEA版本选择。项目基于Spring Boot + MyBatis Plus + Vue + Element,含RBAC权限、多租户、工作流等功能。需理解技术栈、核心功能与数据库设计,并录制8分钟以上讲解视频,提出未懂问题,快速融入团队。
|
弹性计算 Java 测试技术
提交Flink作业及所见问题总结
提交Flink作业及所见问题总结
|
负载均衡 Cloud Native 容灾
阿里云负载均衡SLB价格_ALB、NLB和CLB区别_负载均衡详细介绍
阿里云负载均衡SLB提供ALB、NLB和CLB三种类型,分别适用于7层和4层的不同场景。ALB与NLB仅支持按量付费,而CLB则额外提供包年包月选项。ALB强调7层应用处理与高级路由,NLB聚焦4层的大流量处理与SSL卸载。两者均支持自动弹性伸缩,确保高可用性和性能。CLB作为传统负载均衡,适用于特定需求。每种类型依据实例规格与使用量收费,其中公网实例还需支付网络费用。通过这些服务,用户可以实现流量分发、故障转移及提升应用系统的稳定性和扩展性。
|
监控 关系型数据库 MySQL
一次彻底讲清如何处理mysql 的死锁问题
【10月更文挑战第16天】本文详细介绍了如何处理 MySQL 中的死锁问题,涵盖死锁的概念、原因、检测方法及解决策略,强调通过优化事务设计、调整数据库参数、手动处理和预防措施等手段,有效减少死锁,提升数据库性能与稳定性。
2801 0
|
存储 人工智能 关系型数据库
使用 PostgreSQL pgvector 的 AI 应用程序中的多模态搜索
大型语言模型(LLM)的发展已拓展至多模态领域,不仅能处理文本,还能解析图像。本文介绍如何构建一个多模态搜索应用,用户可通过上传图片或输入文本来搜索印度菜谱。该应用支持多种LLM服务,如OpenAI及Ollama本地部署模型,并运用pgvector扩展在PostgreSQL中高效存储和检索向量嵌入。我们还展示了如何生成菜谱描述的嵌入并向数据库写入这些嵌入,以及如何通过API接口结合文本和图像查询来获取最相关的菜谱结果。此外,讨论了使用分布式SQL数据库如YugabyteDB增强应用的可扩展性和健壮性。
818 1
|
监控 数据可视化 安全
如何查找访问 Nginx 的前 10 个 IP?
【5月更文挑战第1天】
1096 1
如何查找访问 Nginx 的前 10 个 IP?