【时间】2021.11.25
【题目】【《Redis深度历险》读书笔记(5)】应用(5-9)布隆过滤器、简单限流、漏斗限流、GeoHash、Scan
本栏目是《Redis深度历险:核心原理和应用实践》的读书笔记。
目录
一、简介
本章主要主要介绍了Redis 布隆过滤器、简单限流、漏斗限流、GeoHash、Scan的应用。
- 布隆过滤器:进行存在性检测(解决缓存穿透问题),可用来过滤数据,底层实现是bitmap+hash函数,Redis有rebloom布隆过滤器插件module。
- 简单限流:使用zset实现滑动窗口限流(限频),限制一个用户的某种行为(use_id:action_id作为key)在一段时间period内只能进行一定数量。核心是使用时间戳作为score,用zremrangebyscore移除滑动窗口外的数据,使用zcard统计滑动窗口内的数量。
- 漏斗限流:令牌桶限流,为了解决zset实现的简单限流在行为很频繁时滑动窗口中的数据占用大量内存空间而提出的,他不需要保存动作数据。核心是实现funnel漏斗类,包含capacity容量、流出速率等,redis-cell moudle实现了漏斗限流算法。
- GeoHash:底层使用的是zset,将经纬度二维坐标一维化后作为score,value是对应的成员名,可以用来实现类似“附近的朋友”的功能。
- Scan:redis中的所有key保存在一张大表中(hashMap),扫描redis中的key,分slot(即hashMap中的数组元素)一点点遍历的,遍历顺序是高位进位加法顺序。
二、一些重点图
1、布隆过滤器原理
2、布隆过滤器的参数确定
3、简单限流算法的实现(python版)
# coding: utf8
import time
import redis
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: # 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
for i in range(20):
print(is_action_allowed("laoqian", "reply", 60, 5))
4、漏斗限流代码(单机非Redis实现)
# coding: utf8
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(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('laoqian', 'reply', 15, 0.5))
5、scan的高位进位顺序加法遍历
三、思维导图
四、扩展阅读
1、布隆过滤器详解
2、Redis 模块安装:module load 指令