redis笔记2 限流、GeoHash和Scan

限流

简单限流

简单限流的思路是,在规定的时间窗口内,给出规定的最大操作数量限制。使用zset结构作为一个用户行为的记录。zsetvaluescore都用来表示操作的时间戳。每次操作前,先把操作时间戳加入zset结构,然后移除超时的操作时间戳;之后比较总的个数和最大个数的关系,用来表示是否可以操作。

import time
import redis

client = redis.StrictRedis()

def is_action_allowed(usr_id, action_key, priod, max_count):
    key = 'hist:%s:%s' % (user_id, action_key)
    now_ts = int(time.time() * 1000) # 毫秒时间戳
    with client.pipiline() as pipe:
        pipe.zadd(key, now_ts, now_ts)
        # 移除超时时间
        pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
        pipe.zcard(key) # 获取当前时间段内总的操作次数
        # 设置超时时间,移除冷操作节约空间
        pipe.expore(key, period + 1)  
        _, _, current_count, _ = pipe.execute()
    return current_count <= max_count

这种做法的缺陷是,如果规定时间段内允许的操作数非常多,假设1s内可以操作 1 0 5 10^5 105次操作,那么需要对应数量的时间戳来存储,浪费空间。

漏斗限流

介绍漏斗限流的思路。漏斗的滴水速度是匀速的,我们可以往漏斗中加水,如果加水的速度小于漏水的速度,那么这个行为是可以允许的;但是如果超过了这个速度,那么是不允许的。

给出单机漏斗限流的基本实现:

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 = {}

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('erick', 'reply', 15, 0.5)

漏斗限流的缺陷:该方式不适用于分布式的系统。因为从funnels这个hash中取出结构,然后把数据放到内存中计算,最后再放回数据的过程不是原子的。这意味着我们需要加锁操作。如果加锁失败,需要重试或者放弃,复杂度高。

Redis-Cell实现漏斗限流

redis的漏斗限流算法,而且提供了原子限流指令。只有一条指令:

cl.throttle erick:reply 15 30 60 1

意义分别是:

  • erick:reply:键值
  • 15:漏斗的容量
  • 30:规定时间内最大的操作个数
  • 60:规定的时间,这里是30个/60s
  • 1:默认值是1,表示当前添加的quota

该指令返回5个值,分别是:

  • 0:0 表示允许,1表示拒绝
  • 15:漏斗容量
  • 14:剩余空间
  • -1:被拒绝了,需要多久之后再重试
  • 2:多长时间后,漏斗完全空出来

GeoHash算法

该算法用于计算距离,算法的核心思想是把二维空间的距离映射到一维上,然后此时再指定元素时,就可以在一维的距离上进行比较了,减少了复杂度。注意,这里的映射是有损映射,但是损失的精度较小,对于附近的人等的应用,这些误差可以忽略。

总共有6个基本的操作,如下:

  • geoadd person 111 112 foofooperson集合中的经纬度是111 & 112
  • geodist person foo1 foo2 km:返回foo1foo2的距离
  • geohash person foo:获取foo的哈希值
  • georadiusbynumber person foo 20 km count 3 asc:返回foo20km内按照升序的3个人,desc表示降序
  • georadius 111 112 20 km withdist count 3 asc:把名称换成了经纬度

注意,GeoHash的底层数据结构是zset,集群环境中,如果单个key的数据量过多,会对数据迁移造成卡顿。同时,key集合的大小一般也不要超过1MB。所以实际部署GeoHash时,最好单独部署一套,不要和集群混合在一起。

Scan

keys regex是搜索出所有满足regex正则表达式的key值,但是该方式一般在key很少的时候使用,该方式复杂度是 O ( N ) O(N) O(N),会阻塞redis。

一般对于线上服务来说,更多的是使用scan命令,该命令的特点

  • 复杂度O(N),通过游标分步,不会阻塞线程。
  • 提供limit参数,控制返回结果的最大数量
  • 提供模式匹配
  • 返回结果可能有重复,需要客户端去重
  • 遍历过程中,如果数据有修改,则结果是不确定的
  • 单次返回结果为空,不意味着遍历结束,需要看游标是否是0

比如:

scan 0 match key99* count 1000

redis采用渐进式的rehash技术,每次只是同步部分hash数据;同时,redis的遍历方式是采用比特位的高位加法。具体可以参考下面的博客。

对于hashzset等结构,也有对应的hscanzscan等的方式。

参考博客:

  • http://tech-happen.site/32ad6396.html

大key扫描

redis中,如果有很大的hash或者set等的结构,那么可能会造成卡顿,可能的原因如下:

  • 如果key的结构空间不够,则需要重新分配空间,并拷贝数据,耗费时间
  • 集群之间迁移数据,速度变慢、

如果redis的内存消耗出现较大波动,或者耗时出现较大波动,说明可能出现大key,此时需要借助scan的方式扫描并判断:

redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值