令牌桶限流

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
日志服务 SLS,月写入数据量 50GB 1个月
简介: 令牌桶算法比较简单,它就好比摇号买房,拿到号的人才有资格买,没拿到号的就只能等下次了(还好小编不需摇号,因为买不起!)。

1、简介

令牌桶算法比较简单,它就好比摇号买房,拿到号的人才有资格买,没拿到号的就只能等下次了(还好小编不需摇号,因为买不起!)。

在实际的开发中,系统会维护一个容器用于存放令牌(token),并且系统以一个固定速率往容器中添加令牌(token),这个速率通常更加系统的处理能力来权衡。当客户端的请求打过来时,需要从令牌桶中获取到令牌(token)之后,这个请求才会被处理,否则直接拒绝服务。

令牌桶限流的关键在于发放令牌的速率和令牌桶的容量。

实现令牌桶限流的方式有很多种,本文讲述的是基于Redis的Redis-Cell限流模块,这是Redis提供的适用于分布式系统、高效、准确的限流方式,使用十分广泛,而且非常简单!

2、Redis-Cell的安装

Redis默认是没有集成Redis-Cell这个限流模块的,就好比Redis使用布隆过滤器一样,我们也需要对该模块进行安装与集成。

2.1 GitHub源码&安装包

Redis-Cell的GitHub地址:

https://github.com/brandur/redis-cell

Redis-Cell基于Rust语言开发,如果不想花费精力去搞Rust环境,那么可以直接下载与你的操作系统对应的安装包(这个很关键,我就安装了挺多次的,如果安装的问题比较多的话,也建议降低一个release版本!)


下载对应的安装包:

https://github.com/brandur/redis-cell/releases/download/v0.3.0/redis-cell-v0.3.0-armv7-unknown-linux-gnueabihf.tar.gz

如果不清楚自己的服务器(Linux)版本的,可以事先查看后再下载安装包:

# Linux 查看当前操作系统的内核信息

uname -a

# 查看当前操作系统系统的版本信息

cat /proc/version


2.2 安装&异常处理

  • 在Redis的安装目录的同级目录下,新建文件夹Redis-Cell,将压缩包上传后解压

tar -zxvf redis-cell-v0.2.5-powerpc64-unknown-linux-gnu.tar.gz

  • 解压后出现如下文件,复制libredis_cell.so文件的路径(pwd查看当前路径

  • 修改Redis配置文件,redis.conf,添加完成后记得保存后再退出

  • 重启Redis,如果启动正常,进入redis客户端,通过module list查看挂载的模块是否有Redis-Cell

  • 测试指令,出现如下情况说明集成Redis-Cell成功

  • 如果重启Redis后,客户端无法连接成功,说明Redis启动失败,这个时候我们需要查看Redis的启动日志,如果已经配置日志文件的可以直接查看日志定位问题,如果还未配置日志文件的需要先配置日志文件,redis.conf添加日志文件路径地址,再次重启,查看日志文件输出的错误日志

  • 错误可能千奇百怪,问题不大搞技术就不要心急,一个个解决,我这里记录下我最后遇到的问题,/lib64/libc.so.6: version `GLIBC_2.18‘ not found

43767:M 08 Sep 2021 21:39:39.643 # Module /usr/local/soft/Redis-Cell-0.3.0/libredis_cell.so failed to load: /lib64/libc.so.6: version `GLIBC_2.18' not found (required by /usr/local/soft/Redis-Cell-0.3.0/libredis_cell.so)

43767:M 08 Sep 2021 21:39:39.643 # Can't load module from /usr/local/soft/Redis-Cell-0.3.0/libredis_cell.so: server aborting

  • 缺失GLIBC_2.18,那就安装它(最后两个编译的过程时间比较长,耐心等待几分钟)

yum install gcc

wget http://ftp.gnu.org/gnu/glibc/glibc-2.18.tar.gz

tar zxf glibc-2.18.tar.gz

cd glibc-2.18/

mkdir build

cd build/

../configure --prefix=/usr

make -j4

make install

  • 安装完成后,重启Redis,测试是否安装成功,循环上面的过程,通过日志分析错误即可

3、CL.THROTTLE指令

指令CL.THROTTLE参数含义

CL.THROTTLE liziba  10  5 60 1

              ▲     ▲  ▲  ▲ ▲

              |     |  |  | └───── apply 1 token (default if omitted) (本次申请一个token)

              |     |  └──┴─────── 5 tokens / 60 seconds  (60秒添加5个token到令牌桶中)

              |     └───────────── 10 max_burst (最大的突发请求,不是令牌桶的最大容量)

              └─────────────────── key "liziba" (限流key)

输出参数值含义

127.0.0.1:6379> cl.throttle liziba 10 5 60 1

1) (integer) 0     # 当前请求是否被允许,0表示允许,1表示不允许

2) (integer) 11     # 令牌桶的最大容量,令牌桶中令牌数的最大值

3) (integer) 10      # 令牌桶中当前的令牌数

4) (integer) -1     # 如果被拒绝,需要多长时间后在重试,如果当前被允许则为-1

5) (integer) 12     # 多长时间后令牌桶中的令牌会满

这里唯一有歧义的可能是max_burst,这个并不是令牌桶的最大容量,从作者的README.md中的解释也可以看出来

The total limit of the key (max_burst + 1). This is equivalent to the common X-RateLimit-Limit HTTP header.


4、Java调用Redis-Cell模块实现限流

4.1 导入依赖

<dependency>

 <groupId>io.lettuce</groupId>

 <artifactId>lettuce-core</artifactId>

 <version>5.3.4.RELEASE</version>

 <!--排除 netty 包冲突-->

 <exclusions>

   <exclusion>

     <groupId>io.netty</groupId>

     <artifactId>netty-buffer</artifactId>

   </exclusion>

   <exclusion>

     <groupId>io.netty</groupId>

     <artifactId>netty-common</artifactId>

   </exclusion>

   <exclusion>

     <groupId>io.netty</groupId>

     <artifactId>netty-codec</artifactId>

   </exclusion>

   <exclusion>

     <groupId>io.netty</groupId>

     <artifactId>netty-transport</artifactId>

   </exclusion>

 </exclusions>

</dependency>

4.2 实现代码

Redis命令接口定义:

package com.lizba.redis.limit.tokenbucket;


import io.lettuce.core.dynamic.Commands;

import io.lettuce.core.dynamic.annotation.Command;


import java.util.List;


/**

* <p>

*      Redis命令接口定义

* </p>

*

* @Author: Liziba

* @Date: 2021/9/8 23:50

*/

public interface IRedisCommand extends Commands {



   /**

    * 定义限流方法

    *

    * @param key           限流key

    * @param maxBurst      最大的突发请求,桶容量等于maxBurst + 1

    * @param tokens        tokens 与 seconds 是组合参数,表示seconds秒内添加个tokens

    * @param seconds       tokens 与 seconds 是组合参数,表示seconds秒内添加个tokens

    * @param apply         当前申请的token数

    * @return

    */

   @Command("CL.THROTTLE ?0 ?1 ?2 ?3 ?4")

   List<Object> throttle(String key, long maxBurst, long tokens, long seconds, long apply);


}


Redis-Cell令牌桶限流类定义:

package com.lizba.redis.limit.tokenbucket;


import io.lettuce.core.RedisClient;

import io.lettuce.core.api.StatefulRedisConnection;

import io.lettuce.core.dynamic.RedisCommandFactory;


import java.util.List;


/**

* <p>

*      Redis-Cell令牌桶限流

* </p>

*

* @Author: Liziba

* @Date: 2021/9/8 23:47

*/

public class TokenBucketRateLimiter {


   private static final String SUCCESS = "0";

   private RedisClient client;

   private StatefulRedisConnection<String, String> connection;

   private IRedisCommand command;


   public TokenBucketRateLimiter(RedisClient client) {

       this.client = client;

       this.connection = client.connect();

       this.command = new RedisCommandFactory(connection).getCommands(IRedisCommand.class);

   }



   /**

    * 请是否被允许

    *

    * @param key

    * @param maxBurst

    * @param tokens

    * @param seconds

    * @param apply

    * @return

    */

   public boolean isActionAllowed(String key, long maxBurst, long tokens, long seconds, long apply) {

       List<Object> result = command.throttle(key, maxBurst, tokens, seconds, apply);

       if (result != null && result.size() > 0) {

           return SUCCESS.equals(result.get(0).toString());

       }

       return false;

   }


}

测试代码:

package com.lizba.redis.limit.tokenbucket;


import io.lettuce.core.RedisClient;


/**

* <p>

*      测试令牌桶限流

*      测试参数 cl.throttle liziba 10 5 60 1

* </p>

*

* @Author: Liziba

* @Date: 2021/9/9 0:02

*/

public class TestTokenBucketRateLimiter {


   public static void main(String[] args) {

       RedisClient client = RedisClient.create("redis://192.168.211.108:6379");

       TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(client);

       // cl.throttle liziba 10 5 60 1

       for (int i = 1; i <= 15; i++) {

           boolean success = limiter.isActionAllowed("liziba", 10, 5, 60, 1);

           System.out.println("第" + i + "次请求" + (success ? "成功" : "失败"));

       }


   }


}

测试结果(这里也说明了令牌桶的容量是max_burst + 1):

第0次请求成功

第1次请求成功

第2次请求成功

第3次请求成功

第4次请求成功

第5次请求成功

第6次请求成功

第7次请求成功

第8次请求成功

第9次请求成功

第10次请求成功

第11次请求成功

第14次请求失败

第15次请求失败

第14次请求失败

第15次请求失败

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
打赏
0
0
0
0
10
分享
相关文章
使用令牌桶和漏桶实现请求限流逻辑
使用令牌桶和漏桶实现请求限流逻辑
110 0
使用漏桶和令牌桶实现API速率限制
本文介绍如何在 Go 语言的 Gin 框架中使用漏桶算法和令牌桶算法实现 API 限流,以保护系统资源,防止过载和恶意攻击,确保服务稳定。通过具体代码示例展示了两种算法的应用方法。
46 2
Go 分布式令牌桶限流 + 兜底保障
Go 分布式令牌桶限流 + 兜底保障
【令牌桶算法与漏桶算法】
【令牌桶算法与漏桶算法】
187 0
详解 Java 限流接口实现问题之避免令牌桶限流算法可能导致的过载问题如何解决
详解 Java 限流接口实现问题之避免令牌桶限流算法可能导致的过载问题如何解决
SpingCloud 限流的令牌桶算法和漏桶算法
SpingCloud 限流的令牌桶算法和漏桶算法
171 1
常见的限流算法分析以及手写实现(计数器、漏斗、令牌桶)
限流是指在高并发、大流量请求的情况下,限制新的流量对系统的访问,从而保证系统服务的安全性。
1720 0
常见的限流算法分析以及手写实现(计数器、漏斗、令牌桶)
面试常见问题-限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景
面试常见问题-限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景
773 0
token bucket令牌桶限流算法原理及代码(下)
token bucket令牌桶限流算法原理及代码
475 0
token bucket令牌桶限流算法原理及代码(下)
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等