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

news2024/11/28 17:45:59

限流系列

开源组件 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 入门使用简介 & 源码分析

漏桶算法

漏桶算法,又称 leaky bucket。

为了理解漏桶算法,我们看一下对于该算法的示意图:

image

从图中我们可以看到,整个算法其实十分简单。首先,我们有一个固定容量的桶,有水流进来,也有水流出去。

对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。

我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。

所以漏桶算法天生不会出现临界问题

漏桶算法可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

java 实现

/*
 * Copyright (c)  2018. houbinbin Inc.
 * rate-acquire All rights reserved.
 */

package com.github.houbb.rate.limit.core.core.impl;

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 org.apiguardian.api.API;

/**
 * 漏桶算法
 *
 * @author houbinbin
 * Created by bbhou on 2017/9/20.
 * @since 0.0.7
 */
@API(status = API.Status.EXPERIMENTAL)
public class LimitLeakyBucket extends LimitAdaptor {

    private static final Log LOG = LogFactory.getLog(LimitLeakyBucket.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 water;

    /**
     * 上一次的更新时间
     *
     * @since 0.0.6
     */
    private volatile long lastUpdateTime;

    /**
     * 构造器
     *
     * @param context 上下文
     * @since 0.0.4
     */
    public LimitLeakyBucket(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.water = 0;
        this.lastUpdateTime = System.currentTimeMillis();
    }

    /**
     * 获取锁
     *
     * (1)未满加水:通过代码 water +=1进行不停加水的动作。
     * (2)漏水:通过时间差来计算漏水量。
     * (3)剩余水量:总水量-漏水量。
     *
     * @since 0.0.5
     */
    @Override
    public synchronized boolean acquire() {
        long now = System.currentTimeMillis();
        // 先执行漏水,计算剩余水量
        long durationMs = now - lastUpdateTime;
        long leakyWater = (long) (durationMs * 1.0 * rate / 1000);
        LOG.debug("[Limit] leaky water is " + leakyWater);
        water = Math.max(0, water - leakyWater);


        // 这里应该加一个判断,如果漏水量较小,直接返回。避免开始时,大量流量通过
        if(leakyWater < 1) {
            LOG.debug("[Limit] leaky water is too small!");
            return false;
        }

        if ((water + 1) < capacity) {
            // 尝试加水,并且水还未满
            water++;

            lastUpdateTime = now;
            return true;
        } else {
            // 水满,拒绝加水
            LOG.debug("[Limit] leaky water is has been full!");
            return false;
        }
    }

}

和令牌桶核心的不同,是出的速度实际上是固定的。

不会像令牌同时可以消费多个的情况。

当水桶满时,和令牌满是类似的,直接返回 false。

测试

public class LimitLeakyBucketTest {

    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(5)
            .limit(LimitLeakyBucket.class)
            .build();

    static class LimitRunnable implements Runnable {
        @Override
        public 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();
    }

}
  • 日志
23:55:54.911 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-0
23:55:55.113 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-1
23:55:55.313 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-2
23:55:55.513 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-3
23:55:55.713 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-4
23:55:55.913 [Thread-1] INFO  com.github.houbb.rate.limit.core.core.impl.LimitTokenBucket - Thread-1-5

主要问题

因为漏桶的漏出速率是固定的,因此它对于存在突发特性的流量来说缺乏效率。

算法对比

计数器 VS 滑动窗口:

计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。

滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。

漏桶算法 VS 令牌桶算法:

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。

因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。

当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。

单点应用限流 vs 分布式集群限流

单点应用下,对应用进行限流,既能满足本服务的需求,又可以很好的保护好下游资源。

在选型上,可以采用Google Guava的RateLimiter即可。

而在多机部署场景下,对单点的限流,则不能达到最好效果,需要引入分布式限流。分布式限流的算法,依然可以采用令牌桶算法,只不过将令牌桶的发放、存储改为全局的模型。

真正实现中,可以采用redis+lua的方式,通过把逻辑放在redis端,来减少调用次数。

lua的逻辑如下:

1,redis中存储剩余令牌的数量cur_token,和上次获取令牌的时间last_time。

2,在每次申请令牌时,可以根据(当前时间cur_time - last_time)的时间差 乘以 令牌发放速率,算出当前可用令牌数。

3,如果有剩余令牌,则准许请求通过;否则不通过。

拓展阅读

guava RateLimiter 源码

常见的限流组件:

Sentinel

Hystrix

resilience4j

参考资料

「限流算法第四把法器:漏桶算法」

限流限速RateLimiter

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

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

相关文章

.ts文件编译为.js文件

.ts文件如何编译为.js文件 首先安装了tsc $ npm install -g typescript可以使用如下命令检查是否安装tsc,出现版本号则说明安装成功 tsc -v创建.ts文件 创建 1.ts&#xff0c;编写代码如下&#xff1a; function test(a:string):string{return a }编译为.js文件 执行如下…

vue2中vuex状态管理使用安装教程及多模块化拆分,包含大多项目常用用法

前言 所有模块的vuex文件堆在一起太难看了 拆成多个&#xff0c;所有项目都这么做 vue2版本的vuex 脚手架搭建 npm install -g vue/cli vue create vuexDemo cd vuexDemo步骤 1&#xff09;安装 npm i vuex32&#xff09;快速入门 学过一次后老是记不住&#xff1f;害…

视觉的边界填充、数值计算和腐蚀操作

文章目录 一、边界填充二、数值计算三、图片融合四、腐蚀操作 一、边界填充 边界填充的4种方法&#xff1a; # 导入OpenCV库&#xff0c;用于图像处理 import cv2 # 导入matplotlib的pyplot模块&#xff0c;用于图像显示 import matplotlib.pyplot as plt # 导入numpy库&…

【Unity】Unity中接入Admob聚合广告平台,可通过中介接入 AppLovin,Unity Ads,Meta等渠道的广告

一、下载Google Admob的SDK插件 到Google Admob官网中&#xff0c;切换到Unity平台 进来之后是这样&#xff0c;注意后面有Unity标识&#xff0c;然后点击下载&#xff0c;跳转到github中&#xff0c;下载最新的Admob插件sdk&#xff0c;导入到Unity中 二、阅读官方文档&…

js 实现斐波那契数列

斐波那契数列&#xff1a;所谓斐波那契数列指的是数列&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;21&#xff0c;……。即数列满足递推公式&#xff0c;F(0)0&#xff0c;F(1)1 实现代码&#xff1a; 第一种&…

【记录】Django数据库的基础操作

数据库连接 在Django中使用 mysqlclient 这个包用于数据库的连接&#xff0c;切换至 Django环境中直接 pip install mysqlclient 安装此包 1 数据库连接配置 在项目目录下的setting.py中配置 DATABASES {default: {ENGINE: django.db.backends.mysql,NAME: mini,#数据库名US…

(IOS)VMware虚拟机上安装win10系统(超详细)

简介 虚拟机是一种软件实现的计算机系统&#xff0c;可以在现有的操作系统平台上运行一个或多个虚拟的操作系统。它通过在主机操作系统上创建一个虚拟的硬件平台&#xff0c;并在其上运行一个完整的操作系统&#xff0c;来模拟一个真实的物理计算机。虚拟机可以提供一种隔离的…

青少年编程能力等级测评CPA C++(二级)试卷(1)

青少年编程能力等级测评CPA C&#xff08;二级&#xff09;试卷&#xff08;1&#xff09; 一、单项选择题&#xff08;共20题&#xff0c;每题3.5分&#xff0c;共70分&#xff09; CP2_1_1&#xff0e;下列C程序段中&#xff0c;对二维数组arr的正确定义是&#xff08; &am…

PL/SQL Developer如何连接Oracle数据库(汉化)

简介 PL/SQL Developer是一种用于Oracle数据库开发的集成开发环境&#xff08;IDE&#xff09;。它提供了一个可视化的界面&#xff0c;使开发人员能够方便地编写、调试和执行PL/SQL代码。PL/SQL Developer还具有其他功能&#xff0c;如数据库对象浏览器、SQL编辑器、数据导入…

python深浅拷贝,可变变量与不可变变量

赋值 在 python 中&#xff0c;赋值是将一个值或对象分配给一个变量的过程。赋值操作符是 &#xff0c;用于将右侧的值或对象赋给左侧的变量。 赋值&#xff1a;l2的值会随着原对象l1的值一同改变 l1 [1, 2, 3, 4] print(l1:, l1) l2 l1 print(l2:, l2) 给li列表新增元素 …

Java基础:面向对象编程3

1 Java可变长参数 1.1 概述 Java 的可变长参数&#xff08;Varargs&#xff09;是在 Java 1.5 中引入的功能&#xff0c;允许方法接受任意数量的相同类型的参数。可变参数的语法是在参数类型后面加上三个点&#xff08;...&#xff09;&#xff0c;例如 int... numbers。 1.…

Zsh 安装与配置

目录 1 环境配置 1.1 基本工具安装 1.2 安装 oh-my-zsh 1.3 从.bashrc中迁移配置&#xff08;可选&#xff09; 2 主题配置 2.1 内置主题 2.2 自定义主题 2.2.1 推荐主题 3 插件安装 3.1 推荐插件 3.1.1 zsh -autosuggestions 3.1.2 zsh-syntax-highlighting 3.2 启…

kubernetes中的微服务

目录 一 什么是微服务 二 微服务的类型 三 ipvs模式 3.1 ipvs模式配置方式 四 微服务类型详解 4.1 clusterip 4.2 ClusterIP中的特殊模式headless 4.3 nodeport 4.4 loadbalancer 4.5 metalLB 4.6 externalname 五 Ingress-nginx 5.1 ingress-nginx功能 5.2 部署…

【数据结构】1.顺序表

「前言」 &#x1f308;个人主页&#xff1a; 代码探秘者 &#x1f308;C语言专栏&#xff1a;C语言 &#x1f308;C专栏&#xff1a; C &#x1f308;喜欢的诗句:天行健,君子以自强不息. 线性表 线性表&#xff08;List&#xff09;&#xff1a;零个或多个数据元素的有限序列…

软考(网工)——数据通信基础

&#x1f550;信道特性 1️⃣概念 通信得目的就是传递信息。通信中产生和发送信息得一端叫信源&#xff0c;接受信息的一段叫信宿&#xff0c;信源和信宿之间的通信线路称为信道。 2️⃣信道带宽 W 模拟信道&#xff1a;Wf2-f1&#xff08;f2 和 f1分别表示&#xff1a;信道…

树的中心——dfs

题目 代码 #include <bits/stdc.h> using namespace std; const int N 1e510, M N*2; int h[N], e[M], ne[M], idx; int n; int ans 2e9; bool st[N]; void add(int a, int b) // 添加一条边a->b {e[idx] b, ne[idx] h[a], h[a] idx ; } int dfs(int u) {int…

芯片记录一下

1、MC34063 电源管理DCDC 输入电压&#xff1a;-0.3~40V 输出电压&#xff1a;Vout1.25*&#xff08;1R2/R1&#xff09; 1.25V~40V

【报错解决】安装scikit-rebate包报错

scikit-rebate ReBATE是一套基于Relief的机器学习特征选择算法 报错信息 解决方案 conda install numpy scipy scikit-learnpip install skrebate依次运行以上两步&#xff0c;即可成功安装&#xff01;

如何实时监测你的光纤资源?

光纤资源作为重要的通信基础设施&#xff0c;实时监测光纤资源的状态是运营管理好光纤资源的重要手段&#xff0c;那常用的监测指标维度与方法有那些呢&#xff1f; 维度1&#xff1a;资源数量 资源数量主是建立资源的基础档案&#xff0c;掌握光缆的型号、路由&#xff1b;光…

健康推荐系统:SpringBoot技术实现

3系统分析 3.1可行性分析 通过对本基于智能推荐的卫生健康系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本基于智能推荐的卫生健康系统采用SSM框架&#…