java redis 限流_Redis——限流算法之滑动窗口、漏斗限流的原理及java实现

本文介绍了限流的重要性,以及如何使用Redis实现滑动窗口和漏斗限流算法。通过滑动窗口算法利用Redis的ZSet数据结构限制用户行为,而漏斗算法则通过模拟漏水过程限制请求速率。此外,还提到了Redis-cell模块提供的原子性限流指令以及令牌桶限流算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

限流的意义

限流一般是指在一个时间窗口内对某些操作请求的数量进行限制,比如一个论坛限制用户每秒钟只能发一个帖子,每秒钟只能回复5个帖子。限流可以保证系统的稳定,限制恶意请求,防止因为流量暴增导致系统瘫痪宕机。

常用的限流算法有:滑动窗口、漏斗以及令牌桶。

得益于redis的数据结构特点,redis实现滑动窗口限流和漏斗限流的非常的便捷。

滑动窗口限流的原理和实现

以xx论坛限制用户行为为例子,比如一秒内进行某个操作50次,这种行为应该进行限制。

滑动窗口就是记录一个滑动的时间窗口内的操作次数,操作次数超过阈值则进行限流。

网上找的图:

8e069e98ab594be314c2764e458e777e.png

在redis中可以用zset数据结构来实现这个功能:

用唯一的id作为zset的key,可以是user_id + action_key ,value是当前操作的时间戳。每次新的操作请求进来时,先判断当前时间窗口内记录的操作次数 count,小于阈值max则允许进行操作,超过阈值则进行限流。同时对时间窗口之外的数据进行清理,节省内存。

简单代码实现:

public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {

// 生成唯一的key

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

long nowTs = System.currentTimeMillis();

// 使用管道

Pipeline pipe = jedis.pipelined();

pipe.multi();

// 添加当前操作当zset中

pipe.zadd(key, nowTs, "" + nowTs);

// 整理zset,删除时间窗口外的数据

pipe.zremrangeByScore(key, 0, nowTs - period * 1000);

Response count = pipe.zcard(key);

pipe.expire(key, period + 1);

pipe.exec();

pipe.close();

return count.get() <= maxCount;

}

复制代码

zset中的value没有特殊含义,只是用来保证每次操作都是唯一的能够被zset记录。

漏斗限流

顾名思义就是用一个漏斗来存储(记录请求),一边向漏斗里面加请求一边将请求漏出去。漏斗的漏嘴有一个流水速率,表示单位时间内流出的水量(数据量),当加水的速率(单位时间加进去的请求)小于流水速率时漏斗永远不会满。

我们不用时刻记录==漏水==,只需记录上一次漏水的开始时间,当一个请求进来时,我们只需要计算上次漏水的时间到当前时间一共漏出的数据量count,用这上次漏水至今的数据总量减去count,了,来判断漏斗是否溢出

漏斗算法的Java实现:

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;

}

if (deltaQuota < 1) { // 腾出空间太小,最小单位是 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;

}

}

// 用一个HashMap存储漏斗数据

private Map funnels = new HashMap<>();

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

// 唯一key

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);

}

return funnel.watering(1); // 需要 1 个 quota

}

}

复制代码

Redis-Cell

Redis中提供的一个限流Redis模块:redis-cell ,该模块使用漏斗算法并提供原子性的限流指令。

该redis指令:

> cl.throttle key capacity operations times 1

key:唯一的key代表该漏斗

capacity:漏斗初始容量

operations times : 该时间长度内可以进行的最大操作数量,比如 30 60 表示60s内的操作次数最大是30 。

1 : 是一个可选参数,默认值是1

复制代码

额外加餐——令牌桶限流算法

谷歌开源项目Guava中的RateLimiter使用的限流算法就是令牌桶限流算法。

网上找的图

a81c70188eb84970240422bf6325f4d4.png

以恒定的速度生成令牌并将令牌加到一个桶里,如果桶满了将其丢弃

每个请求进来时都会到桶里拿一个令牌,如果没拿到(桶里没有令牌)拒绝该请求,若拿到则正常执行。

Redis,限流算法,Redis 限流算法,滑动窗口,漏斗限流,谷歌令牌桶限流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值