限流功能的实现

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 限流功能的实现

前言


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


正文


假设限制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"   // 多长时间令牌放满


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
10月前
|
存储 算法 NoSQL
接口限流
防止用户恶意刷新接口, 防止对接口的恶意请求,减少不必要的资源浪费,从而保证服务可用。如果不做限流,任由某个用户以非正常方式高频率访问的话,会直接将网络流量、服务器资源挤占完,从而影响正常对外提供服务,造成服务不可用。
114 1
|
3月前
|
缓存 Java 应用服务中间件
常见的限流降级方案
【1月更文挑战第21天】
|
3月前
|
监控 测试技术 数据安全/隐私保护
如何集成Sentinel实现流控、降级、热点规则、授权规则总结
如何集成Sentinel实现流控、降级、热点规则、授权规则总结
142 0
|
算法 NoSQL JavaScript
服务限流,我有6种实现方式…
服务限流,我有6种实现方式…
|
缓存 NoSQL 算法
限流实现-专题一
在实际业务中,经常会碰到突发流量的情况。如果公司基础架构做的不好,服务无法自动扩容缩容,在突发高流量情况下,服务会因为压力过大而崩溃。更恐怖的是,服务崩溃如同多米诺骨牌,一个服务出问题,可能影响到整个公司所有组的业务。
|
缓存 算法 网络协议
限流实现2
剩下的几种本来打算能立即写完,没想到一下三个月过去了,很是尴尬。本次主要实现如下两种算法 - 令牌桶算法 - 漏斗算法
你的系统支持限流吗?
很多时候为了系统的安全性以及负载考虑,我们都会在请求量过高的时候对系统进行限流处理,那么今天我就我自己做过的一个系统里面的限流方案来介绍一下
94 0
|
Java 开发者 Sentinel
SentineI 服务熔断降级的策略 | 学习笔记
快速学习 SentineI 服务熔断降级的策略
105 0
|
监控 算法 安全
限流
1. 为什么需要限流 2. 如何限流 限流主要就是考虑这两点
233 0
限流
|
算法
​什么是限流,如何限流
​什么是限流,如何限流
255 0
​什么是限流,如何限流