高可用之限流-07-token bucket 令牌桶算法

限流系列

开源组件 rate-limit: 限流

高可用之限流-01-入门介绍

高可用之限流-02-如何设计限流框架

高可用之限流-03-Semaphore 信号量做限流

高可用之限流-04-fixed window 固定窗口

高可用之限流-05-slide window 滑动窗口

高可用之限流-06-slide window 滑动窗口 sentinel 源码

高可用之限流-07-token bucket 令牌桶算法

高可用之限流 08-leaky bucket漏桶算法

高可用之限流 09-guava RateLimiter 入门使用简介 & 源码分析

令牌桶算法

令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。

典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。

Google的Guava包中的RateLimiter类就是令牌桶算法的解决方案。

漏桶算法和令牌桶算法的选择

漏桶算法与令牌桶算法在表面看起来类似,很容易将两者混淆。但事实上,这两者具有截然不同的特性,且为不同的目的而使用。

漏桶算法与令牌桶算法的区别在于,漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。

需要注意的是,在某些情况下,漏桶算法不能够有效地使用网络资源,因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。

因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。

通常,漏桶算法与令牌桶算法结合起来为网络流量提供更高效的控制。

算法描述与实现

描述

假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中(每秒会有r个令牌放入桶中);

假设桶中最多可以存放b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;

当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌(不同大小的数据包,消耗的令牌数量不一样),并且数据包被发送到网络;

如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外(n个字节,需要n个令牌。该数据包将被缓存或丢弃);

算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。

对于在流量限制外的数据包可以以不同的方式处理:

1)它们可以被丢弃;

2)它们可以排放在队列中以便当令牌桶中累积了足够多的令牌时再传输;

3)它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。

实现

添加令牌的时机

我们当然不用起一个定时任务不停的向桶中添加令牌,只要在访问时记下访问时间,下次访问时计算出两次访问时间的间隔,然后向桶中补充令牌,补充的令牌数为

(long) (durationMs * rate * 1.0 / 1000)

其中rate是每秒补充令牌数。

这里可以前傻傻的一个任务不停的执行对比起来,还是比较巧妙的。

初步实现

import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rate.limit.core.core.ILimitContext;
import com.github.houbb.rate.limit.core.util.ExecutorServiceUtil;
import org.apiguardian.api.API;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;/*** 令牌桶算法** @author houbinbin* Created by bbhou on 2017/9/20.* @since 0.0.6*/
public class LimitTokenBucket extends LimitAdaptor {private static final Log LOG = LogFactory.getLog(LimitTokenBucket.class);/*** 令牌的发放速率* <p>* 每一秒发放多少。** @since 0.0.6*/private final long rate;/*** 容量* <p>* 后期暴露为可以配置** @since 0.0.6*/private final long capacity;/*** 令牌数量** @since 0.0.6*/private volatile long tokenNum;/*** 上一次的更新时间** @since 0.0.6*/private volatile long lastUpdateTime;/*** 构造器** @param context 上下文* @since 0.0.4*/public LimitTokenBucket(final ILimitContext context) {// 暂不考虑特殊输入,比如 1s 令牌少于1 的场景long intervalSeconds = context.timeUnit().toSeconds(context.interval());this.rate = context.count() / intervalSeconds;// 8 的数据this.capacity = this.rate * 8;// 这里可以慢慢的加,初始化设置为0// 这样就有一个 warmUp 的过程this.tokenNum = 0;this.lastUpdateTime = System.currentTimeMillis();}/*** 获取锁** @since 0.0.5*/@Overridepublic synchronized boolean acquire() {if (tokenNum < 1) {// 加入令牌long now = System.currentTimeMillis();long durationMs = now - lastUpdateTime;long newTokenNum = (long) (durationMs * 1.0 * rate / 1000);LOG.debug("[Limit] new token is " + newTokenNum);if (newTokenNum > 0) {// 超过的部分将舍弃this.tokenNum = Math.min(newTokenNum + this.tokenNum, capacity);lastUpdateTime = now;} else {// 时间不够return false;}}this.tokenNum--;return true;}}

测试

  • Test.java
import com.github.houbb.heaven.util.util.TimeUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rate.limit.core.bs.LimitBs;
import com.github.houbb.rate.limit.core.core.ILimit;
import com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket;/*** <p> project: rate-limit-LimitTokenBucketTest </p>* <p> create on 2020/6/22 22:38 </p>** @author binbin.hou* @since 0.0.6*/
public class LimitTokenBucketTest {private static final Log LOG = LogFactory.getLog(LimitTokenBucket.class);/*** 2S 内最多运行 5 次* @since 0.0.5*/private static final ILimit LIMIT = LimitBs.newInstance().interval(1).count(10).limit(LimitTokenBucket.class).build();static class LimitRunnable implements Runnable {@Overridepublic void run() {for(int i = 0; i < 6; i++) {while (!LIMIT.acquire()) {// 等待令牌TimeUtil.sleep(100);}LOG.info("{}-{}", Thread.currentThread().getName(), i);}}}public static void main(String[] args) {new Thread(new LimitRunnable()).start();}}
  • 日志
22:47:19.084 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-0
22:47:19.186 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-1
22:47:19.286 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-2
22:47:19.386 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-3
22:47:19.486 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-4
22:47:19.586 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-5

相对来说令牌桶还是比较平滑的。

小结

令牌桶算法是网络流量整形和速率限制中最常使用的一种算法。

典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。

大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。

传送到令牌桶的数据包需要消耗令牌。不同大小的数据包,消耗的令牌数量不一样。令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节。如果令牌桶中存在令牌,则允许发送流量;而如果令牌桶中不存在令牌,则不允许发送流量。

因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。

原理

ps: 原理相对枯燥,理解即可。

令牌桶是网络设备的内部存储池,而令牌则是以给定速率填充令牌桶的虚拟信息包。每个到达的令牌都会从数据队列领出相应的数据包进行发送,发送完数据后令牌被删除。

请求注解(RFC)中定义了两种令牌桶算法——单速率三色标记算法和双速率三色标记算法,其评估结果都是为报文打上红、黄、绿三色标记。

QoS会根据报文的颜色,设置报文的丢弃优先级,其中单速率三色标记比较关心报文尺寸的突发,而双速率三色标记则关注速率上的突发,两种算法都可工作于色盲模式和非色盲模式。

以下结合这两种工作模式介绍一下RFC中所描述的这两种算法。

1)单速率三色标记算法

网络工程师任务小组(IETF)的RFC文件定义了单速率三色标记算法,评估依据以下3个参数:承诺访问速率(CIR),即向令牌桶中填充令牌的速率;承诺突发尺寸(CBS),即令牌桶的容量,每次突发所允许的最大流量尺寸(注:设置的突发尺寸必须大于最大报文长度);超额突发尺寸(EBS)。

一般采用双桶结构:C桶和E桶。Tc表示C桶中的令牌数,Te表示E桶中令牌数,两桶的总容量分别为CBS和EBS。初始状态时两桶是满的,即Tc和Te初始值分别等于CBS和EBS。令牌的产生速率是CIR,通常是先往C桶中添加令牌,等C桶满了,再往E桶中添加令牌,当两桶都被填满时,新产生的令牌将会被丢弃。

色盲模式下,假设到达的报文长度为B。若报文长度B小于C桶中的令牌数Tc,则报文被标记为绿色,且C桶中的令牌数减少B;若 Tc < B < Te,则标记为黄色,E和C桶中的令牌数均减少B;若 B > Te,标记为红色,两桶总令牌数都不减少。

在非色盲模式下,若报文已被标记为绿色或 B < Tc,则报文被标记为绿色,Tc减少B;若报文已被标记为黄色或 Tc < B < Te,则标记为黄色,且Te减少B;若报文已被标记为红色或 B > Te,则标记为红色,Tc和Te都不减少。

2)双速率三色标记算法

IETF的RFC文件定义了双速率三色算法,主要是根据4种流量参数来评估:CIR、CBS、峰值信息速率(PIR),峰值突发尺寸(PBS)。前两种参数与单速率三色算法中的含义相同,PIR这个参数只在交换机上才有,路由器没有这个参数。该值必须不小于CIR的设置值,如果大于CIR,则速率限制在CIR于PRI之间的一个值。

与单速率三色标记算法不同,双速率三色标记算法的两个令牌桶C桶和P桶填充令牌的速率不同,C桶填充速率为CIR,P桶为PIR;两桶的容量分别为CBS和PBS。用Tc和Tp表示两桶中的令牌数目,初始状态时两桶是满的,即Tc和Tp初始值分别等于CBS和PBS。

色盲模式下,如果到达的报文速率大于PIR,超过Tp+Tc部分无法得到令牌,报文被标记为红色,未超过Tp+Tc而从P桶中获取令牌的报文标记为黄色,从C桶中获取令牌的报文被标记为绿色;当报文速率小于PIR,大于CIR时,报文不会得不到令牌,但超过Tp部分报文将从P桶中获取令牌,被标记为黄色报文,从C桶中获取令牌的报文被标记为绿色;当报文速率小于CIR时,报文所需令牌数不会超过Tc,只从C桶中获取令牌,所以只会被标记为绿色报文。

在非色盲模式下,如果报文已被标记为红色或者超过Tp+Tc部分无法得到令牌的报文,被标记为红色;如果标记为黄色或者超过Tc未超过Tp部分报文记为黄色;如果报文被标记为绿或未超过Tc部分报文,被标记为绿色。

拓展阅读

guava 源码解析

漏桶算法实现

参考资料

漏桶算法&令牌桶算法理解及常用的算法

流量控制算法——漏桶算法和令牌桶算法

Token Bucket 令牌桶算法

华为-令牌桶算法

简单分析Guava中RateLimiter中的令牌桶算法的实现

网络拥塞及其对策

令牌桶算法限流

程序员必会 | 限流限速RateLimiter

令牌桶(TokenBucket)限流 - Java实现

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重构案例:将纯HTML/JS项目迁移到Webpack

我们已经了解了许多关于 Webpack 的知识&#xff0c;但要完全熟练掌握它并非易事。一个很好的学习方法是通过实际项目练习。当我们对 Webpack 的配置有了足够的理解后&#xff0c;就可以尝试重构一些项目。本次我选择了一个纯HTML/JS的PC项目进行重构&#xff0c;项目位于 GitH…

web3学习-区块链基础知识

1.1 区块链技术简史 block chain 点对点的分布式交易系统 比特币协议并不是图灵完备的。 以太坊协议加入了智能合约&#xff0c;智能合约是以太坊协议与比特币协议的最大区别&#xff08;图灵完备&#xff09; 1.2、区块链设计哲学 去中心化 由于没有中心化的数据库作为…

记录一个容易混淆的 Spring Boot 项目配置文件问题

记录一个容易混淆的 Spring Boot 项目配置文件问题 去年&#xff0c;我遇到了这样一个问题&#xff1a; 在这个例子中&#xff0c;由于密码 password 以 0 开头&#xff0c;当它被 Spring Boot 的 bean 读取时&#xff0c;前导的 0 被自动去掉了。这导致程序无法正确读取密码。…

网盘直链下载神器NDM

工具介绍 ​Neat Download Manager分享一款网盘不限速神器,安装步骤稍微有一点繁琐,但实际体验下载速度飞快,个人实际体验还是非常不错的 NDM是一款免费且强大的下载工具。可以帮助你下载各种文件&#xff0c;还能够在多任务下载中保持出色的速度及其稳定性 通过网盘分享的文…

【MySQL核心面试题】MySQL 核心 - Explain 执行计划详解!

欢迎关注公众号 【11来了】&#xff08;文章末尾即可扫码关注&#xff09; &#xff0c;持续 中间件源码、系统设计、面试进阶相关内容 在我后台回复 「资料」 可领取 编程高频电子书&#xff01; 在我后台回复「面试」可领取 30w 字的硬核面试笔记&#xff01; 感谢你的关注&…

【ios】在 SwiftUI 中实现可随时调用的加载框

在 SwiftUI 项目中实现一个自定义的加载框&#xff08;loading&#xff09;功能&#xff0c;可以在任意位置调用&#xff0c;以便显示加载动画或者进度条。下面的教程将详细讲解如何创建一个可复用的 Loading 组件&#xff0c;并通过通知机制控制其显示和隐藏。 先上效果&…

MySQL【知识改变命运】10

联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例&#xff1a;⼀个完整的联合查询的过程2.2.1. 确定参与查询的表&#xff0c;学⽣表和班级表2.2.2. 确定连接条件&#xff0c;student表中的class_id与class表中id列的值相等2.…

wordpress 子比主题美化 四宫格 多宫格 布局插件

wordpress 主题美化 四宫格 多宫格 布局插件&#xff08;只在子比主题上测试过&#xff0c;其它主题没测试&#xff09; A5资源网四宫格布局插件是一个功能丰富的WordPress插件,专为创建自适应的四宫格布局而设计。这个插件具有以下主要特点: 灵活的布局: 支持1到8个宫格的自定…

nginx代理配置,搞定proxy_pass配置

nginx代理配置 容易晕的proxy_pass 后面 url带不带”/“的问题代理配置举例 常用代理配置前端页面代理前端请求代理后端请求代理 容易晕的proxy_pass 后面 url带不带”/“的问题 &#xff08;1&#xff09;配置 proxy_pass 时&#xff0c;当在后面的 url 加上了 /&#xff0c;…

Java中的泛型——类型介绍与使用详解

1.什么是泛型 泛型&#xff08;Generics&#xff09;是 Java 提供的一种特性&#xff0c;使得类、接口、方法可以操作各种类型的对象&#xff0c;而不必在编写时指定具体的类型。泛型允许我们在定义时使用类型参数&#xff0c;并在实际使用时为这些参数提供具体的类型。泛型的主…

Springboot整合knife4j生成文档

前言 在开发过程中&#xff0c;接口文档是很重要的内容&#xff0c;用于前端对接口的联调&#xff0c;也用于给其他方使用。但是手写相对比较麻烦。 当然也有swagger之类的&#xff0c;但是界面没有那么友好。 官网&#xff1a; 整合步骤 整合依赖 需要根据版本进行&…

Java方法的递归调用

Java中的方法可以通过调用自身来实现递归调用。 递归调用在解决一些问题时非常有用&#xff0c;特别是那些可以分解为相同结构的子问题的情况。递归调用可以让问题的解决过程更加简洁和优雅。 下面是一个简单的示例&#xff0c;展示了如何使用递归调用来计算一个数字的阶乘&a…

如何使用 pnpm 进行打补丁patch操作?推荐两个方法

前言 作为一个前端开发者&#xff0c;我们每天都在和各种各样的库和依赖打交道。node_modules 目录中存放着我们项目的各种依赖。我们有时需要对其中的一些依赖进行修改&#xff0c;比如修复某个 bug 或者增加某些自定义功能。这时候&#xff0c;给 node_modules 打补丁就显得…

软考高级系统架构设计师 知识产权

是记录&#xff0c;这一章内容是选择题&#xff0c;分值是两到三分之间 计算机保护条例 软件地定义 软件是软件及其文档 软件开发者 即写代码或者组织开发地&#xff0c;并对开发完成地软件承担责任地法人软件著作权人&#xff0c;是对软件享有著作权地自然人、法人或者其他组…

springboot 对接Telegram发送消息

官方maven包项目 https://repo.maven.apache.org/maven2/org/telegram/ 官方github仓库 https://github.com/rubenlagus 发送消息demo代码 PostMapping("/test")public void test(RequestParam("chatId") String chatId,RequestParam("messageTe…

准备用esp32-c3搞一个无线开关

思路&#xff1a; 用两片c3,一片作为开关&#xff0c;另一片作为http servet控制灯泡&#xff0c;两者采用wifi 连接 开关&#xff1a;GPIO 2脚电阻上拉&#xff0c;平时始终为高电平&#xff0c;开灯时按下按钮&#xff0c;2脚接地&#xff0c;电平为低电平。再用wifi把此信…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量&#xff1a;从 2…

中小型企业网络的设计与实现

资料下载中小型企业网络的设计与实现论文资源-CSDN文库 摘 要 本文规划的是一个公司的网络搭建&#xff0c;网络设计包括了多个部门的网络架构&#xff0c;每个部门通过VLAN进行隔离&#xff0c;确保了网络的安全性和高效。 华为企业网络模拟平台&#xff08;ENSP&#xff09…

androidStudio编译导致的同名.so文件冲突问题解决

files found with path lib/arm64-v8a/libserial_port.so from inputs: ...\build\intermediates\library_jni\debug\jni\arm64-v8a\libserial_port.so C:\Users\...\.gradle\caches\transforms-3\...\jni\arm64-v8a\XXX.so 解决方式如下&#xff1a; 1.将gradle缓存文件删…

TwinCAT3安装 Advanced Motion Pack库

文章目录 一.简介二.安装方式1. 下载地址2. 双击下载好的安装包3. 选择语言&#xff08;只有英文和德语&#xff09;4. 点击Next5. 选择Accept6. 填写公司和组织名称&#xff08;随意&#xff09;7. 点击Install8. 等待安装完成9. 点击Finish 一.简介 TF5420 TC3 Motion Pick-…