Redis限流方案

限流简介

限流算法在分布式领域是一个经常被提起的话题,当系统的处理能力有限时,如何阻止计划外的请求继续对系统施压,是一个需要重视的问题。

除了控制流量,限流还有一个应用目的是用于控制用户行为,避免垃圾请求,比如在UGC社区,用户的发帖、回复、点赞等行为都要严格受控,一般要严格限定某行为在规定时间内允许的次数,超过了次数那就是非法行为。对于非法行为,业务必须规定适当的惩处策略。

简单限流

首先我们来看一个常见的简单限流策略。系统要限定用户某个行为在指定的时间里只能发生N次,如何使用Redis的数据结构来实现这个限流功能。

首先,这个接口定义如下

#指定用户use_id的某个行为action_key在特定的时间内period只允许发生一定的次数
int max_count;
boolean is_action_allowed(user_id,action_key,period,max_count){
 return true;
}
# 调用这个接口,一分钟内只允许最多回复5个贴子
can_reply = is_action_allowed("user1","reply",60,5);
if(can_reply) do();
else return ;

解决方案
这个限流需求中存在一个滑动时间窗口,在zset数据结构中提供了zremrangebyscore key-name min max指令用于移除有序集合中分值介于min和max之间的元素集合。因此可以使用行为发生的时间戳作为zset集合中元素的score,时间戳越大,相应score越高。 而且我们只需要保留这个时间窗口,窗口之外的数据都可以砍掉,从而节省内存。

示意图如下
在这里插入图片描述
代码如下:

package example;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

public class SimpleRateLimiter {
    private Jedis jedis;
    public SimpleRateLimiter(Jedis jedis) {
        this.jedis = jedis;
    }
    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();
        pipe.zadd(key, nowTs, "" + nowTs);
        pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
        Response<Long> count = pipe.zcard(key);
        pipe.expire(key, period + 1);
        pipe.exec();
        pipe.close();
        return count.get() <= maxCount;
    }
    public static void main(String[] args) {
        Jedis jedis = new Jedis();
        SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
        for (int i = 0; i < 20; i++) {
            System.out.println(limiter.isActionAllowed("laoqian", "reply", 60, 5));
        }
    }
}

代码整体思路:每一个行为到来时,都维护一次时间窗口,将时间窗口外的记录全部清理掉,只保留窗口内的记录。zset集合中只有score值非常重要,value值没有特别意义。只需要保证他的唯一性即可。

因为这几个连续的Redis操作都是针对同一个key的,使用pipeline可以显著提升Redis存取效率。但是这种方案也有缺点,因为他要记录时间窗口内所有的行为记录。如果这个量很大,比如限定60s内操作不得超过100w次这样的参数,他是不适合做这样的限流的,因为会消耗大量的存储空间。

高级限流算法——漏洞限流

漏洞限流是最常用的限流方法之一,这个算法的灵感来源于漏斗的结构。漏斗的容量是有限的,如果将漏嘴堵住,然后一直往里面灌水,他就会变满,直至再也装不进去。如果将漏嘴放开,水就会往下流,流走一部分之后,就可以继续往里面灌水。如果漏嘴流水的速率大于灌水的速率。那么漏洞永远都不会装满。如果漏嘴流水速率小雨灌水速率,那么一旦漏斗满了,灌水就需要暂停并等待漏斗腾空。

所以,漏斗的剩余空间就代表着当前行为可以持续进行的数量,漏嘴的流水速率代表着系统允许该行为的最大频率。

单机漏斗算法:

package example;

import java.util.HashMap;
import java.util.Map;

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;
        }
    }
    private Map<String, Funnel> funnels = new HashMap<>();
    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);
        }
        return funnel.watering(1); // 需要 1quota 
    }
}

Funnel对象的make_space方法是漏斗算法的核心,其次在每次灌水前都会被调用以触发漏水,给漏斗腾出空间。能腾出多少空间取决于过去了多久以及流水的速率。Funnel对象占据的空间大小不再和行为的频率成正比,她的空间占用是一个常量。

分布式限流 Redis-Cell

Redis-Cell语法
在这里插入图片描述

127.0.0.1:6379> cl.throttle mytest 99 5 100 2
1) (integer) 0                        #0 表示成功, 1表示失败
2) (integer) 100                      # 令牌桶的容量
3) (integer) 98                       # 当前令牌桶的令牌数
4) (integer) -1                       # 成功时该值为-1,失败时表还需要等待多少秒可以有足够的令牌
5) (integer) 41                       # 预计多少秒后令牌桶会满

多次从令牌桶中取出数据

127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 0
2) (integer) 100
3) (integer) 54
4) (integer) -1
5) (integer) 911
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 0
2) (integer) 100
3) (integer) 14
4) (integer) -1
5) (integer) 1708
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 1                      #失败,拒绝取出
2) (integer) 100
3) (integer) 14
4) (integer) 505                  # 取出失败,令牌桶还有14个令牌,还需505秒才能够取出
5) (integer) 1705

代码示例

package example;

import io.rebloom.client.Client;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;

public class RedisCellExample {
    private Jedis jedis;
    public RedisCellExample() {
        jedis = new Jedis("127.0.0.1", 6379);
    }
    public Boolean rush() {
        //对接口进行限流操作
        //检查令牌桶,返回的第一值是否为 0: 0-流量够,1-限流中
        String script = "return redis.call('cl.throttle',KEYS[1],ARGV[1],ARGV[2],ARGV[3],ARGV[4])";
        List<String> keys = new ArrayList<>();
        keys.add("redbag");
        String maxBurst = "99";  //漏洞容量
        String countPerPeriod = "10";
        String period = "100";
        String quantity = "10";
        List<String> values = new ArrayList<>();
        values.add(maxBurst);
        values.add(countPerPeriod);
        values.add(period);
        values.add(quantity);
        List<Integer> list = (List) jedis.eval(script, keys, values);
        if (!list.isEmpty() && list.get(0) == 0) {
            return true;
        } else {
            return false;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/687129.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【echarts】如何制作,横坐标每个日期点如何对应一条竖线的图,以及 markline设置后不生效问题

图的样式如下&#xff1a; 在线演示 每一个日期&#xff0c;对应一条竖线展示。 echarts配置内容&#xff1a; 在线演示 option {xAxis: {type: category,data: [20240601, 20240602, 20240603, 20240604, 20240605, 20240606, 20240607] // X轴数据},yAxis: {type: valu…

Bond 网卡绑定技术学习

前言&#xff1a; 为了实现网卡的高可用性&#xff0c;需要学习一下 Bond技术 1. 概念 Bond&#xff08;也被称为链路聚合、端口绑定或接口绑定&#xff09;是一种网络技术&#xff0c;用于将多个物理网络接口&#xff08;如以太网接口&#xff09;组合成一个逻辑接口。这样做…

今日份动态规划学习

主要只搞了一个这道题&#xff0c;有点摸鱼了今天晚上&#xff0c;也是来小看一下这道题吧01背包完全背包 P1941 [NOIP2014 提高组] 飞扬的小鸟 题意&#xff1a; 这题是说&#xff0c;给我们一个游戏界面&#xff0c;界面的长度为n&#xff08;水平距离&#xff09;&#x…

E: Unable to locate package ros-kinetic-usb-cam

mkdir -p USB/src && cd USB/src catkin_init_workspace git clone https://github.com/bosch-ros-pkg/usb_cam.git cd .. catkin_make source devel/setup.bash echo "source ~/USB/devel/setup.bash" >> ~/.bashrc source ~/.bashrc 编译过程报错&…

Wireshark抓包日常运维实用过滤

0x0 Wireshark 介绍 Wireshark 是一款功能强大的网络分析工具&#xff0c;适用于网络专业人员。它提供了出色的过滤器&#xff0c;您可以轻松放大到您认为可能存在问题的位置。过滤器的主要好处是消除定位流量&#xff0c;并缩小要查找的数据类型。 0x1 根据源 IP 地址过滤主…

Golang | Leetcode Golang题解之第134题加油站

题目&#xff1a; 题解&#xff1a; func canCompleteCircuit(gas []int, cost []int) int {for i, n : 0, len(gas); i < n; {sumOfGas, sumOfCost, cnt : 0, 0, 0for cnt < n {j : (i cnt) % nsumOfGas gas[j]sumOfCost cost[j]if sumOfCost > sumOfGas {break}…

四川古力未来科技抖音小店开创电商新纪元,前景广阔值得期待!

在数字化浪潮汹涌的当下&#xff0c;电商行业正以前所未有的速度蓬勃发展。四川古力未来科技抖音小店&#xff0c;作为这一领域的佼佼者&#xff0c;凭借其独特的经营理念和创新的营销策略&#xff0c;正在开创电商行业的新纪元。本文将深入探讨四川古力未来科技抖音小店的前景…

25 - 销售分析III(高频 SQL 50 题基础版)

25 - 销售分析III -- where 是分组之前筛选数据 -- having 是分组之后筛选数据selectp.product_id,p.product_name fromSales s left join Product p on s.product_idp.product_id group byproduct_id havingmin(sale_date) >"2019-01-01" and max(sale_date)&…

京东商品采集以及应用场景||电商API接口

京东商品采集的步骤和应用场景可以归纳如下&#xff1a; 一、采集步骤 注册账号&#xff1a;首先&#xff0c;需要在京东开放平台注册一个开发者账号。创建应用&#xff1a;登录开放平台后&#xff0c;创建一个应用以获取API密钥和应用凭据。获取权限&#xff1a;根据所需的服…

Java学习【深入探索包装类和泛型】

Java学习【深入探索包装类和泛型】 &#x1f680;包装类获取包装类对象的方式使用valueOf()创建直接赋值 Integer成员方法 &#x1f680;泛型引出泛型泛型类泛型方法泛型接口泛型的继承和通配符泛型的上界 在Java的学习中&#xff0c;包装类和泛型是两个重要的概念&#xff0c;…

JVM基础知识

一、JVM的内存区域划分 一个进程在运行的时候,会向操作系统申请到内存资源,从来存放程序运行的相关数据。 JVM本质上就是一个java进程,在运行的时候也会从操作系统那搞一块内存&#xff0c;供Java代码执行使用。 JVM又把申请的一块内存根据不同的用途划分出了不同区域。 每一…

数据库——多表查询概述

与单表查询不同&#xff0c;多表查询是从多张表中查找数据。例如&#xff1a; select * from user,course; 得到一张有36条数据的表&#xff0c;这是因为12条数据的user表与3条数据的course表进行了笛卡尔积运算&#xff0c;但是在多表查询中&#xff0c;往往需要消除笛卡尔积带…

MySQL 与 PostgreSQL 在 SQL 方面的关键对比二(功能篇)

目录 1 详细示例 1.1自动增量列 1.2 字符串连接 1.3 JSON 支持 2 总结 MySQL 和 PostgreSQL 是两种流行的开源关系数据库管理系统&#xff08;RDBMS&#xff09;。尽管它们在许多方面相似&#xff0c;但在 SQL 语法和功能上存在一些显著差异。 以下SQL语句的执行如果需要开…

YoloV8改进策略:Block篇|MobileNetV4——移动生态系统的通用模型

文章目录 摘要1、引言2、相关工作3、硬件无关的帕累托效率4、通用反向瓶颈5、Mobile MQA6、MNv4模型设计6.1、精炼NAS以增强架构6.2、MNv4模型的优化 7、结果7.1、ImageNet分类 8、增强蒸馏方案9、结论10、致谢A、搜索空间细节B、基准测试方法论C、ImageNet-1k分类任务的训练设…

springboot启动报端口被占用,修改端口还是报被占用,如何处理?

第一种方式&#xff1a; 通过cmd查看是否有程序占用端口 netstat -ano| findstr 端口号 杀死进程 taskkill -f -pid 进程号 如果未看到有程序占用该端口说明不是这个原因 第二种方式&#xff1a; 打开任务管理器 查看是否进程占用对应端口&#xff0c;有就关闭进程 第三种…

视觉SLAM十四讲:从理论到实践(Chapter8:视觉里程计2)

前言 学习笔记&#xff0c;仅供学习&#xff0c;不做商用&#xff0c;如有侵权&#xff0c;联系我删除即可 一、目标 1.理解光流法跟踪特征点的原理。 2.理解直接法是如何估计相机位姿的。 3.实现多层直接法的计算。 特征点法存在缺陷&#xff1a; 二、光流(Optical Flow) …

nodeJS社区新冠人群管理与老人疫苗小程序-计算机毕业设计源码65190

目 录 摘要 1 绪论 1.1背景及意义 1.2国内外研究慨况 1.3B/S体系工作原理 1.4node.js主要功能 2 1.5论文结构与章节安排 3 2 社区新冠人群管理与老人疫苗小程序分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5…

【会议征稿,IEEE出版】第六届电子与通信,网络与计算机技术国际学术会议(ECNCT 2024,7月19-21)

第六届电子与通信&#xff0c;网络与计算机技术国际学术会议 &#xff08;ECNCT 2024&#xff09;将于 2024年7月19日-21日 在 中国广州 举办&#xff0c; 为期三天。 会议由广东工业大学自动化学院主办&#xff0c;会议将安排主旨报告&#xff0c;口头报告以及海报展示&#…

人类语言处理nlp部分笔记——二、BERT和它的家族-介绍和微调

参考自李宏毅课程-人类语言处理 二、BERT和它的家族-介绍和微调 1. What is pre-train model 这里所说的pre-train model是输入一串tokens&#xff0c;能够输出一串vectors&#xff0c;且每个vector可以表示对应的语义的模型&#xff0c;这些vectors也被称作为embeddings。以…

假设检验学习笔记

1. 假设检验的基本概念 1.1. 原假设&#xff08;零假设&#xff09; 对总体的分布所作的假设用表示&#xff0c;并称为原假设或零假设 在总体分布类型已知的情况下&#xff0c;仅仅涉及总体分布中未知参数的统计假设&#xff0c;称为参数假设 在总体分布类型未知的情况下&#…
最新文章