Redis学习(11)--限流相关

断尾求生–简单限流

背景:定义一个简单接口,表示系统要限定用户的某个行为在指定的时间里只能允许发生N次。

# 指定用户 user_id 的某个行为 action_key 在特定的时间内 period 只允许发生最多的次数 max_count
def is_action_allowed(user_id,action_key,period,max_count):
	return true
# 调用这个接口,一分钟内只允许最多回复5个帖子
can_reply = is_action_allowed("laoqian","reply",60,5)
if can_reply:
	do_reply()
else:
	raise ActionThresholdOverflow()

解决方案: 从上述代码可以看到限流需求中存在一个滑动时间窗口(定宽),因此考虑zset数据结构的score值,value值保持唯一性即可,用uuid会比较浪费空间,可以改用毫秒时间戳。

var (
	RD = redis.Pool{
		Dial: func() (conn redis.Conn, err error) {
			conn, err = redis.Dial("tcp", "127.0.0.1")
			return
		}}
)

func main() {
	isActionAllowed("laoqian", "reply", 60, 5)
}

/**
 * @Description: 是否允许行为
 */
func isActionAllowed(userId, actionKey string, period, maxCount int64) bool {
	key := fmt.Sprintf("hist:%s:%s", userId, actionKey)
	nowTime := time.Now().Unix()

	conn := RD.Get()
	defer conn.Close()
	_, _ = conn.Do("ZADD", key, nowTime, nowTime)
	_ = conn.Send("ZCOUNT", "action", nowTime-1000*period, nowTime)
	_ = conn.Flush()
	result, _ := conn.Receive()
	count := cast.ToInt64(result)
	return count <= maxCount
}

使用pipeline可以显著提升Redis存取效率。但这种方案也有缺点,因为它要记录时间窗口内所有的行为记录,如果这个量很大,比如 "限定60s内操作不得超过100万次"之类,它是不适合做这样的限流的,因为会消耗大量的存储空间。

一毛不拔-漏斗限流

Golang版本单机漏斗算法如下:

var (
	funnels = make(map[string]*Funnel)
)

/**
 * @Description: 是否允许此行为
 */
func isActionAllowed(userId, actionKey string, capacity int, leakingRate float64) bool {
	key := fmt.Sprintf("%s:%s", userId, actionKey)
	if funnel, ok := funnels[key]; !ok {
		funnel = NewFunnel(capacity, leakingRate)
		funnels[key] = funnel
		return funnel.watering(1)
	} else {
		return funnel.watering(1)
	}
}

/**
 * @Description: 漏斗对象
 */
type Funnel struct {
	capacity    int     //漏斗容量
	leakingRate float64 //漏嘴流水速率
	leftQuota   int     //漏斗剩余空间
	leakingTs   int64   //上一次漏水时间
}

/**
 * @Description: 创建一个漏斗对象
 */
func NewFunnel(capacity int, leakingRate float64) *Funnel {
	return &Funnel{
		capacity:    capacity,
		leakingRate: leakingRate,
		leftQuota:   capacity,
		leakingTs:   time.Now().Unix(),
	}
}

/**
 * @Description: 计算漏斗空间
 */
func (f *Funnel) makeSpace() {
	nowTs := time.Now().Unix()
	deltaTs := nowTs - f.leakingTs
	deltaQuota := int(float64(deltaTs) * f.leakingRate)
	//间隔时间太长,整数数字过大溢出
	if deltaQuota < 0 {
		f.leftQuota = f.capacity
		f.leakingTs = nowTs
		return
	}
	//腾出空间太小,最小单位是1
	if deltaQuota < 1 {
		return
	}
	f.leftQuota += deltaQuota
	f.leakingTs = nowTs
	if f.leftQuota > f.capacity {
		f.leftQuota = f.capacity
	}
}

/**
 * @Description: 通过漏斗往漏斗里面进行灌水
 */
func (f *Funnel) watering(quota int) bool {
	f.makeSpace()
	if f.leftQuota >= quota {
		f.leftQuota -= quota
		return true
	}
	return false
}

redis4.0提供了一个限流Redis模块,它叫Redis-Cell。该模块也使用了漏斗算法,并提供了原子的限流指令。

> cl.throttle laoqian:reply 15 30 60 1  
1) (integer) 0 # 0表示允许,1表示拒绝
2) (integer) 15 # 漏斗容量 capacity
3) (integer) 14 # 漏斗剩余空间 left_quota
4) (integer) -1 # 如果被拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

  • 15 表示 capacity 漏斗容量
  • 30 operations /60 seconds 这是漏水速率

cl.throttle 指令的重试时间是返回结果数组的第四个值进行sleep即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值