java redis 限流_细品服务并发限流+Redis-cell的使用

背景

今天热搜“海底捞的排号系统挂掉了”,也许是今天情人节,各位情侣去海底捞约会,进入排号系统的流量猛增,导致服务支撑不住,直接挂掉,在这里只是猜测(大胆猜测,小心求证)。那我们应该如何防止因为流量突然猛增而导致服务挂掉的问题呢?那就是限流了。

那我们通过redis 来设计限流策略。

服务限流

简介

通过压测我们可以压出我们服务接口可以承受最大的QPS或者TPS,但是我们压测的话只是单压并不知道在生产环境所能承受的最大流量。如果说其他业务接口也在跑,那这就很难把控这个接口在生产环境可以定多大的QPS或TPS。所以预估某个接口的所能承受的QPS和TPS还是很有水平的。我能力有限今天只聊如何限流。

时间窗口限流

什么是时间窗口?

TCP/IP为了提高传输效率(提高吞吐量)采用并发进行传输包,由于有ACk机制,如果等并发发出去的包都回来的话,会影响整体的发送效率,所以只要等到他需要等待的数据就进行发起第二次的传输。有人就会问了,TCP包的传输是有序的,如果并发发送的包,顺序是在后面的包先回来了,那怎么搞,那就继续等待先去的包回来再进行下次操作。

还有就是采用了并发那还得考虑机器的性能,可不能由于发送的包太多导致发送包的服务不可用了。于是就有了滑动时间窗口协议。

滑动时间窗口主要解决的问题就是控制瞬时的量,通过缓存将其分为4个区段,进行滑动处理这4个区段。当这四个区段放满后就会进行等待。想了解得更有深度,可以点击上面链接进行深度学习。下图是TCP滑动窗口示意图

87df5f406616b691d15def845060b581.png

时间滑动窗口协议的应用

现在我们的服务使用的是java语言,现在需要实现一个滑动窗口。

2.使用ReentrantLock(可重入锁)实现,如下图 这样有个问题就是:粒度太大了,不均匀,针对1秒一下的,没法辨析。

我们能不能把粒度拆细了,1秒拆成10个100毫秒。每一个100毫秒有一个计数器。了解TCP/IP的应该知道,TCP/IP为了增加传输速度和控制传输速度,有个叫“滑动窗口协议”。就算拆得再细,也无法解决匀速限制速度的问题。而且还有个临界点问题,比如假如,一秒限制10个请求,在第1秒钟,第2秒 之间,第1秒后半段时间10个请求,第2秒前半段10个请求,那第1秒后半段+第2秒前半段时间组成的一秒钟里就有20个请求,没有起到限速的作用。

a392c5c3a55345951e1ca6447e6b4914.png

java中的JUC包中的CyclicBarrier。实现一个限流。我们只允许最多执行多少个线程。如果其中一个阻塞了。那这就很尴尬了。会影像服务的正常的吞吐,但是上面的那种方式,当他阻塞了后,随着时间窗口的推进,会将上一次时间串口的请求的技术进行归零。

漏斗限流

漏斗限流是最常用的限流方法之一,顾名思义,这个算法的灵感源于漏斗(funnel)的结

构。

bb6ac206f0c4a05bf1e01a47a6080ec0.png

实现一个简单的漏斗算法

package 漏斗算法;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

public class FunnelRateLimiter {

static class Funnel {

//漏斗的容量

int capacity;

//速率

float leakingRate;

//剩余容量

int leftQuota;

long leakingTs;

public Funnel(int capacity, float leakingRate) {

this.capacity = capacity;

this.leakingRate = leakingRate;

this.leftQuota = capacity;

this.leakingTs = System.currentTimeMillis();

}

void makeSpace() {

long nowTs = System.currentTimeMillis();

long deltaTs = nowTs - leakingTs;

int deltaQuota = (int) (deltaTs * leakingRate);

// 间隔时间太长,整数数字过大溢出

if (deltaQuota < 0) {

this.leftQuota = capacity;

this.leakingTs = nowTs;

return;

}

// 腾出空间太小,最小单位是 1

if (deltaQuota < 1) {

return;

}

this.leftQuota += deltaQuota;

this.leakingTs = nowTs;

if (this.leftQuota > this.capacity) {

this.leftQuota = this.capacity;

}

}

boolean watering(int quota) {

makeSpace();

if (this.leftQuota >= quota) {

this.leftQuota -= quota;

return true;

}

return false;

}

}

private Map funnels = new ConcurrentHashMap<>();

public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {

String key = String.format("%s:%s", userId, actionKey);

Funnel funnel = funnels.get(key);

if (funnel == null) {

funnel = new Funnel(capacity, leakingRate);

funnels.put(key, funnel);

}

// 需要 1 个 quota

return funnel.watering(1);

}

}

有人就会怀疑自己写的不靠谱,写在服务里面使用内存这更不靠谱,有没有可以使用的中间件,被人造好的轮子。还真有,redis-cell

Redis-cell 的使用

Redis 4.0 提供了一个限流 Redis 模块,它叫 redis-cell。该模块也使用了漏斗算法,并

提供了原子的限流指令。有了这个模块,限流问题就非常简单了。

该模块只有 1 条指令 cl.throttle,它的参数和返回值都略显复杂,接下来让我们来看看这

个指令具体该如何使用

7609fc06419f0de1b58851e6c02bb0fb.png

上面这个指令的意思是允许「用户laoqian回复行为」的频率为每 60s 最多 30 次(漏水速

率),漏斗的初始容量为 15,也就是说一开始可以连续回复 15 个帖子,然后才开始受漏水

速率的影响。我们看到这个指令中漏水速率变成了 2 个参数,替代了之前的单个浮点数。用

两个参数相除的结果来表达漏水速率相对单个浮点数要更加直观一些。

> cl.throttle laoqian:reply 15 30 60

1) (integer) 0 # 0 表示允许,1 表示拒绝

2) (integer) 15 # 漏斗容量 capacity

3) (integer) 14 # 漏斗剩余空间 left_quota

4) (integer) -1 # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)

5) (integer) 2 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

在执行限流指令时,如果被拒绝了,就需要丢弃或重试。cl.throttle 指令考虑的非常周

到,连重试时间都帮你算好了,直接取返回结果数组的第四个值进行 sleep 即可,如果不想

阻塞线程,也可以异步定时任务来重试(放入一个队列进行消费队列)。

总结

两种时间限流方法 时间窗口限流和漏斗限流

简单介绍了时间窗口限流在TCP中的应用,TCP为了达到并发,且安全可靠传输采用时间窗口协议进行并发可靠传输包

时间窗口的限流方式不能达到顺滑,为达到顺滑限流采用漏都限流。使用java简单实现漏斗限流

Redis4.0 中cell的使用。完美的且简单的就可以实现限流。

参考

《redis 深度历险》

https://juejin.im/entry/6844903695432286215

https://www.jianshu.com/p/41781605ed29

其他限流算法(令牌桶https://www.google.com/search?q=%E4%BB%A4%E7%89%8C%E6%A1%B6&oq=%E4%BB%A4%E7%89%8C%E6%A1%B6&aqs=chrome…69i57j69i59j0l4.1915j0j7&sourceid=chrome&ie=UTF-8)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值