• 《Redis深度历险》五(限流)


    限流Demo

    用zset去做限流

    client = redis.StrictRedis()
    
    def is_action_allowed(user_id,action_key,period,max_count):
    	key = 'hist:%s:%s'%(user_id,action_key)
    	now_ts = int(time.time()*1000)
    	with client.pipeline() as pipe:
    		# 记录行为
    		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)
    		pipe.expire(key, period + 1)
    	# 比较数量是否超标
    	return current_count <= max_count
    

    缺点:比如限定60s不得超过100w次这样的参数,不适合做这样的限流,因为会消耗大量的存储空间。

    Funnels限流

    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 = {} # 所有的漏斗
        
        def is_action_allowed(user_id,action_key,capacity,leaking_rate):
            key = "%s:%s"%(user_id,action_key)
            funnel = funnels.get(key)
        
            if not funnel:
                funnel = Funnel(capacity,leaking_rate)
                funnels[key] = funnel
            return funnel.watering(1)
        
        for i in range(20):
            print(is_action_allowed("jack","reply",15,0.5))
    
    

    但是有个问题,我们无法保证整个过程的原子性。从 hash 结构中取值,然后在内存里 运算,再回填到 hash 结构,这三个过程无法原子化,意味着需要进行适当的加锁控制。而 一旦加锁,就意味着会有加锁失败,加锁失败就需要选择重试或者放弃。

    如果重试的话,就会导致性能下降。如果放弃的话,就会影响用户体验。同时,代码的 复杂度也跟着升高很多。

    Redis-Cell

    cl.throttle

    cl.throttle jack:reply 15 30 60 1
        					key
        									capacity(漏斗容量15)
        											(漏水速率30/60)
        														可选参数(默认值1)
    

    标识允许jack:reply行为每60s最多30次,漏斗的初始容量为15,也就是一开始连续回复15个帖子,才开始受漏水速率的影响。

    cl.throttle jack:reply 15 30 60
    0 # 0标识允许,1表示拒绝
    15 # 漏斗容量
    14 # 漏斗剩余空间
    -1 # 如果拒绝了,需要多久重试(单位秒)
    2  # 多长时间后,漏斗完全空出来
    

    在执行限流指令时,如果被拒绝了,就需要丢弃或重试。cl.throttle 指令考虑的非常周 到,连重试时间都帮你算好了,直接取返回结果数组的第四个值进行 sleep 即可,如果不想 阻塞线程,也可以异步定时任务来重试。

  • 相关阅读:
    『数学』--数论--组合数+卢卡斯定理+扩展卢卡斯定理
    Lucene高亮
    Linux 计划任务
    Lucene.net(4.8.0) 学习问题记录二: 分词器Analyzer中的TokenStream和AttributeSource
    Asp.net Core 异步调用 Task await async 的梳理
    Asp.net core 中的依赖注入
    Lucene.net(4.8.0) 学习问题记录一:分词器Analyzer的构造和内部成员ReuseStategy
    Git 使用篇二:小组协作开发
    Git 使用篇二:搭建远程服务器
    Git 使用篇一:初步使用GitHub,下载安装git,并上传项目
  • 原文地址:https://www.cnblogs.com/jimmyhe/p/14096000.html
Copyright © 2020-2023  润新知