高并发系统设计之限流

news2024/9/25 23:12:47

本文已收录至Github,推荐阅读 👉 Java随想录

文章目录

    • 限流算法
      • 计数器算法
      • 滑动窗口
      • 漏桶算法
      • 令牌桶算法
    • 限流算法实现
      • Guava RateLimiter实现限流
        • 令牌预分配
        • 预热限流
      • Nginx 限流
        • limit_conn
        • limit_req
        • 黑白名单限流

这篇文章来讲讲限流,在高并发系统中限流是必不可少的,限流可以保证一部分的请求得到正常的响应,是一种自我保护的措施。限流可以保证使用有限的资源提供最大化的服务能力,按照预期流量提供服务,超过的部分将会拒绝服务、排队或等待、降级等处理。

首先,先来了解下几种限流算法。

限流算法

计数器算法

计数器算法是限流算法里最简单也是最容易实现的一种算法。

举个例子:我们规定接口A在1分钟内访问次数不能超过1000个。我们可以设置一个计数器,对固定时间窗口1分钟进行计数,每有一个请求,计数器就+1,如果请求数超过了阈值,则舍弃该请求,当时间窗口结束时,重置计数器为0。

计数器算法虽然简单,但是有一个十分致命的问题,那就是临界问题。

假设有一个用户,他在0:59时,瞬间发送了1000个请求,并且1:01又发送了1000个请求,那么其实用户在 2秒里面,瞬间发送了2000个请求。用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能利用这个漏洞卡Bug,瞬间压垮我们的应用。

缺点:没有办法防止时间范围临界点突发大流量,很可能在时间范围交界处被大量请求直接打到降级,影响后续服务

滑动窗口

滑动窗口算法解决了上诉计数器算法的缺点。计数器的时间窗口是固定的,而滑动窗口的时间窗口是动态的。

整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器,比如当一个请求在0:35秒的时候到达,那么0:30~0:39对应的计数器就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59到达的1000个请求会落在灰色的格子中,而1:01到达的请求会落在橘黄色的格子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是2000个,超过了限定的1000个,所以此时能够检测出来触发限流。

当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确

缺点:滑动窗口无法平滑控制请求流量,仅能控制时间段内请求总量,宏观来看,时间轴上的请求数量波形可能出现较大的波动

漏桶算法

说到漏桶算法的时候,我们脑中先构思出一幅图:一个水桶,桶底下有一个小孔,水以固定的频率流出,水龙头以任意速率流入水,当水超过桶则”溢出“

漏桶算法的话保证了固定的流出速率,这是漏桶算法的优点,也可以说是缺点。始终恒定的处理速率有时候并不一定是好事情,对于突发的请求洪峰,在保证服务安全的前提下,应该尽最大努力去响应,这个时候漏桶算法显得有些呆滞,最终可能导致水位”溢出“,请求被丢弃。

缺点:无法应对突发流量,由于处理速度恒定,当大量请求到来时,用户等待时间长,用户体验差

令牌桶算法

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。

缺点:令牌桶的数量,生成的速度需要根据以往的系统性能以及用户习惯等经验的累积来判断,实际限流数难以预知

限流算法实现

Guava RateLimiter实现限流

引入依赖

<dependency>
	<groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

下面是一个使用的简单例子:

import com.google.common.util.concurrent.RateLimiter;

public class RateLimiterTest {
    public static void main(String[] args) {
        RateLimiter rateLimiter = RateLimiter.create(1); //创建一个每秒产生一个令牌的令牌桶
        for (int i = 1; i <= 5; i++) {
            double waitTime = rateLimiter.acquire(i); //一次获取i个令牌
            System.out.println("acquire:" + i + " waitTime:" + waitTime);
        }

    }
}

结果:
    
acquire:1 waitTime:0.0
acquire:2 waitTime:0.995081
acquire:3 waitTime:1.998054
acquire:4 waitTime:2.999351
acquire:5 waitTime:3.999224

可以发现等待时间差不多间隔都是1秒。

img

RateLimiter是个抽象类,子类SmoothRateLimiter又做了层抽象,SmoothRateLimiter有两个子类SmoothBursty和SmoothWarmingUp。

  1. SmoothBursty: 令牌的生成速度恒定。使用 RateLimiter.create(double permitsPerSecond) 创建的是 SmoothBursty 实例。
  2. SmoothWarmingUp:令牌的生成速度持续提升,直到达到一个稳定的值。WarmingUp,顾名思义就是有一个热身的过程。使用 RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 时创建就是 SmoothWarmingUp 实例,其中 warmupPeriod 就是热身达到稳定速度的时间。

SmoothWarmingUp可以理解为是进阶版的SmoothBursty

令牌预分配

RateLimiter 使用令牌桶算法,会进行令牌的累积,令牌会被预先分配。

public class RateLimiterTest {
    public static void main(String[] args) {
        RateLimiter r = RateLimiter.create(5);
        while (true) {
            System.out.println("get 5 tokens: " + r.acquire(5) + "s");
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("end");
            /**
             * output:
             * get 5 tokens: 0.0s
             * get 1 tokens: 0.996766s 滞后效应,需要替前一个请求进行等待
             * get 1 tokens: 0.194007s
             * get 1 tokens: 0.196267s
             * end
             * get 5 tokens: 0.195756s
             * get 1 tokens: 0.995625s 滞后效应,需要替前一个请求进行等待
             * get 1 tokens: 0.194603s
             * get 1 tokens: 0.196866s
             */
        }
    }
}

RateLimiter 由于会累积令牌,所以可以应对突发流量。有一个请求会直接请求5个令牌,但是由于此时令牌桶中有累积的令牌,足以快速响应。 RateLimiter 在没有足够令牌发放时,采用滞后处理的方式,也就是前一个请求获取令牌所需等待的时间由下一次请求来承受,也就是代替前一个请求进行等待。

预热限流

RateLimiter 的 SmoothWarmingUp 是带有预热期的平滑限流,它启动后会有一段预热期,逐步将分发频率提升到配置的速率。

public class RateLimiterTest {
    public static void main(String[] args) {
        RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
        while (true) {
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("get 1 tokens: " + r.acquire(1) + "s");
            System.out.println("end");
            /**
             * output:
             * get 1 tokens: 0.0s
             * get 1 tokens: 1.329289s
             * get 1 tokens: 0.994375s
             * get 1 tokens: 0.662888s  上边三次获取的时间相加正好为3秒
             * end
             * get 1 tokens: 0.49764s  正常速率0.5秒一个令牌
             * get 1 tokens: 0.497828s
             * get 1 tokens: 0.49449s
             * get 1 tokens: 0.497522s
             */
        }
    }
}

创建一个平均分发令牌速率为2,预热期为3秒。令牌桶一开始并不会0.5秒发一个令牌,而是频率越来越高,在3秒钟之内达到原本设置的频率,以后就以固定的频率输出。

介绍几个重要的参数

abstract class SmoothRateLimiter extends RateLimiter {
	//当前存储令牌数
    double storedPermits;
    //最大存储令牌数
    double maxPermits;
    //添加令牌时间间隔
    double stableIntervalMicros;
    private long nextFreeTicketMicros;
}

通过Debug我们可以看到,SmoothBursty方法的最大令牌数被设置成了,maxBurstSeconds * permitsPerSecond,而maxBurstSeconds默认是1。

img

img

而 SmoothWarmingUp最大令牌数的计算方法要复杂的多。

img

Nginx 限流

对于Nginx接入层限流可以使用 Nginx自带的两个模块:连接数限流模块ngx_http _limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module

limit_conn 用来对某个key对应的总的网络连接数进行限流,可以按照如IP、域名维度进行限流。limit_req用来对某个key对应的请求的平均速率进行限流,有两种用法:平滑模式(delay)和允许突发模式(nodelay)。

limit_conn

limit_conn是对某个key对应的总的网络连接数进行限流。可以按照IP来限制IP维度的总连接数,或者按照服务域名来限制某个域名的总连接数。但是,记住不是每个请求连接都会被计数器统计,只有那些被Nginx处理的且已经读取了整个请求头的请求连接才会被计数器统计。

http {
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_conn_log_level error;
    limit_conn_status 503 ;

    server {
        location /limit {
            limit_conn addr l;
        }
    }
}
  • limit_conn:要配置存放key和计数器的共享内存区域和指定key的最大连接数。此处指定的最大连接数是1,表示Nginx最多同时并发处理1个连接,addr就是限流key,对应上文 zone=addr。
  • limit_conn_zone:用来配置限流key及存放key对应信息的共享内存区域大小。此处的key是** b i n a r y r e m o t e a d d r ∗ ∗ ,表示 I P 地址,也可以使用 ∗ ∗ binary_remote_addr**,表示IP地址,也可以使用** binaryremoteaddr,表示IP地址,也可以使用server_name**作为key来限制域名级别的最大连接数。
  • limit_conn_status:配置被限流后返回的状态码,默认返回503。
  • limit_conn_log_level:配置记录被限流后的日志级别,默认error级别。

limit_req

limit_req 是漏桶算法实现,用于对指定key 对应的请求进行限流,比如,按照 IP维度限制请求速率。配置示例如下:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    limit_conn_log_level error;
    limit_conn_status 503;

    server {
        location /limit {
            limit_req zone=one burst=20 nodelay;
        }
    }
}

limit_req 和 limit_conn 的配置类似。

  • limit_req:配置限流区域,上面的参数会让nginx 每个IP一秒钟只处理一个请求。
  • burst: burst 参数定义了超出 zone 指定速率的情况下,客户端还能发起多少请求,超出速率的请求将会被放入队列,我们将队列大小设置为20。这意味着,如果从一个给定 IP 地址发送 21 个请求,Nginx 会立即将第一个请求发送到上游服务器群,然后将余下 20 个请求放在队列中。然后每1秒转发一个排队的请求,只有当传入请求使队列中排队的请求数超过 20 时,Nginx 才会向客户端返回 503。
  • nodelay:配置 burst 参数将会使通讯更流畅,但是可能会不太实用,因为该配置会使站点看起来很慢。在上面的示例中,队列中的第 20 个包需要等待 20 秒才能被转发,此时返回给客户端的响应可能不再有用。要解决这个情况,可以在 burst 参数后添加 nodelay 参数。使用 nodelay 参数,当一个请求到达“太早”时,只要在队列中能分配位置,Nginx 将立即转发这个请求。将队列中的该位置标为”taken”(占据),并且不会被释放以供另一个请求使用,直到一段时间后才会被释放。假设如前所述,队列中有 20 个空位,从给定的 IP 地址发出的 21 个请求同时到达。Ngin x会立即转发这个 21 个请求,并且标记队列中占据的 20 个位置,然后每 1秒释放一个位置。如果是25个请求同时到达,Nginx 将会立即转发其中的 21 个请求,标记队列中占据的 20 个位置,并且返回 503 状态码来拒绝剩下的 4 个请求。如果希望不限制两个请求间允许间隔的情况下实施“流量限制”,nodelay 参数是很实用的。
  • limit_req_zone:配置限流key、存放key对应信息的共享内存区域大小、固定请求速率。此处指定的key是“$binary_remote_addr”,表示IP地址。10m表示共享内存的大小,16000 个 IP 地址的状态信息,大约需要 1MB,所以示例中区域可以存储 160000 个 IP 地址
  • limit_conn_status:配置被限流后返回的状态码,默认返回503。
  • limit_conn_log_level:配置记录被限流后的日志级别,默认级别为error。

黑白名单限流

geo $limit {
    default         1;
    10.0.0.0/8      0;
    192.168.0.0/64  0;
}
map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
server {
    location / {
        limit_req zone=req_zone burst=10 nodelay;
    }
}

geo 指令将给在白名单中的 IP 地址对应的 $limit 变量分配一个值 0,给其它不在白名单中的分配一个值 1。然后我们使用一个映射将这些值转为 key。

白名单内 IP 地址的**$limit_key变量被赋值为空字符串,不在白名单内的被赋值为客户端的 IP 地址。当limit_req_zone**后的第一个参数是空字符串时,不会应用“流量限制”,所以白名单内的 IP 地址不会被限制。其它所有 IP 地址都会被限制到每秒 5 个请求。

而要做出网站黑名单,就有可能要屏蔽一堆ip,但是如果将其放在nginx.conf文件夹下,既不美观,也不利于管理,因此需要单独写出一个conf文件,然后在nginx.conf中使用 include标签引用它。

如果我们不是要限流,而是要直接实现黑名单禁止访问网站的话。可以使用allowdeny标签。

server{
    listen: 80;
    server_name www.baidu.com;
    allow all; #允许访问所有的ip
    deny 172.0.0.1; #禁止 172.0.0.1 访问
}

可以配合shell脚本,然后把脚本加入crontab定时任务就可以实现动态添加黑名单。

#!/bin/bash
#取最近5w条数据
tail -n50000 /usr/local/nginx/logs/access.log \
#过滤需要的信息行ip等
|awk '{print $1,$12}' \
#过滤爬虫
|grep -i -v -E "google|yahoo|baidu|msnbot|FeedSky|sogou|360|bing|soso|403|admin" \
#统计
|awk '{print $1}'|sort|uniq -c|sort -rn \
#超过1000加入黑名单
|awk '{if($1>1000)print "deny "$2";"}' >> /usr/local/nginx/conf/blockip.conf
#重启nginx生效
/usr/local/nginx/sbin/nginx -s reload

本篇文章就到这里,感谢阅读,如果本篇博客有任何错误和建议,欢迎给我留言指正。

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

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

相关文章

【包装工单批次编号不存在】和【MES没有样品单报工数据】

包装工单批次编号不存在 今天在做数据的时候,发现一个诡异的问题,有几个包装工单明细里没有批次编号。 我问了下假捻的同事,他们说很奇怪,有的时候有,有的时候没有。 我又咨询了供应商,供应商说不可能没有,批次编号的业务逻辑是通过物料编码+区分号+等级+包装方式 4个…

多模态机器学习入门——文献阅读(一)Multimodal Machine Learning: A Survey and Taxonomy

文章目录说明论文阅读AbstractIntroductionIntroduction总结Applications&#xff1a;A Historical Perspective补充与总结3 MULTIMODAL REPRESENTATIONS总结Joint Repersentations&#xff08;1&#xff09;总结和附加(一)Joint Repersentations&#xff08;2&#xff09;总结…

问题总结:Map存入的数据丢失类型任意

发现问题&#xff1a;Map存入的数据丢失类型 经常会使用 Map<String&#xff0c;Object> 来用于存储键值对的数据&#xff0c;由于我们使用 Object 类型来接收数字&#xff0c;但是有些时候会出现map并不知道我们传入的是 Long 还是 Integer 。也就是出现数据类型丢失的…

变分推断 | MATLAB实现VBMC变分贝叶斯蒙特卡洛模拟的贝叶斯推断

变分推断 | MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断 目录 变分推断 | MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断效果一览基本介绍研究内容模型描述模型设计参考资料效果一览 基本介绍 MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断。变分贝叶斯蒙特卡洛(VBMC)是…

6年测试经验老鸟:做不好自动化测试,还谈什么高薪?

提起自动化测试&#xff0c;可谓仁者见人&#xff0c;智者见智&#xff0c;心中五味杂陈啊&#xff01;你从任何一个招聘渠道来看最近两年对测试岗位的要求&#xff0c;几乎都要求会自动化测试。而不少人一直认为手工测试才是王道&#xff0c;工作中有的时候也用不到程序&#…

摆烂三年,我从普通二本到春招华为OD上岸啦

萌妹镇楼 年前拿到的意向书 答读者问 薪资情况 定级D2,13K2K,两个月年终奖&#xff0c;周六加班双倍工资&#xff0c;下个月发。每年一次加薪&#xff0c;OD转华为一次加薪。 加班强度 124晚9点&#xff0c;35晚6点&#xff0c;项目紧急的话&#xff0c;周六会安排加班 转…

依赖倒转原则和里氏代换原则详解

初学依赖倒转原则和里氏代换原则时&#xff0c;由于笔者水平有限&#xff0c;并没有看懂书上的专业术语的解释&#xff0c;经过反复摸索和学习&#xff0c;发现里氏代换原则和依赖倒转原则可以一言以蔽之&#xff1a; 里氏代换原则&#xff1a;开发时以抽象为核心&#xff0c;…

CMake常用指令

CMake常用指令一、前言二、基本指令2.1、ADD_DEFINITIONS2.&#xff12;、ADD_DEPENDENCIES2.3、ADD_TEST 与ENABLE_TESTING 指令。2.4、AUX_SOURCE_DIRECTORY2.5、CMAKE_MINIMUM_REQUIRED2.6、EXEC_PROGRAM2.7、FILE 指令2.8、INCLUDE 指令2.9、其他指令三、FIND_系列指令四、…

云计算 概念与技术

如果我倡导的计算机在未来得到使用&#xff0c;那么有一天&#xff0c;计算也可能像电话一样成为共用设施。计算机应用将成为一全新的、重要的产业的基础。 ——John McCarthy 云计算的概念 定义 Garther公司的定义 一种计算方式&#xff0c;能通过Internet技术将可扩展的和…

内核模块调试常用命令整理

一、 模块加载 1.1 最简单的一个驱动 static int __init my_driver_init( void ) {printk("init my_driver\n");return 0; }static void __exit my_driver_exit( void ) {printk("exit my_driver\n"); }module_init( my_driver_init ); module_exit( my_…

Xamarin.Forsm for Android 显示 PDF

背景 某些情况下&#xff0c;需要让用户阅读下发的文件&#xff0c;特别是红头文件&#xff0c;这些文件一般都是使用PDF格式下发&#xff0c;这种文件有很重要的一点就是不能更改。这时候就需要使用原文件进行展示。 Xamarin.Forms Android 中的 WebView 控件是不能直接显示的…

R统计绘图-NMDS、环境因子拟合(线性和非线性)、多元统计(adonis2和ANOSIM)及绘图(双因素自定义图例)

这个推文也在电脑里待了快一年了&#xff0c;拖延症患者&#xff0c;今天终于把它发出来了。NMDS分析过程已经R统计-PCA/PCoA/db-RDA/NMDS/CA/CCA/DCA等排序分析教程中写过了。最近又重新看了《Numerical Ecology with R》一书,巩固一下知识&#xff0c;正好重新整理了一下发出…

火山引擎推出一站式小程序监控方案

背景 小程序作为轻量级的应用发展迅速&#xff0c;国内已有多家小程序厂商相继推出。为了洞察用户真实体验及程序自身运行状况&#xff0c;监控已成为开发套件中必不可少的一环。随着业务愈来愈复杂&#xff0c;各厂商小程序管理后台免费提供的监控能力逐渐满足不了大部分业务…

软考中级之数据库系统(重点)

涉及考点:数据库模式,ER模型,关系代数与元祖演算,规范化理论,并发控制,分布式数据库系统,数据仓库和数据挖掘 数据库模式 三级模式-二级映射 常考选择题 三级模式,两种映射的这种涉及属于层次架构体的设计,这种设计为我们在应用数据库的时候提供了很多便利,同时提高了整个体…

功能性材料深入超级赛道,赋能多行业迭代升级

中国国际胶粘剂及密封剂展览会深耕胶粘剂、密封剂和胶粘带行业26年&#xff0c;是行业认可的、优质的贸易与技术交流平台。展会连接了十几个行业的买家和卖家&#xff0c;包括汽车、电子、新能源、轨道交通、工业等重要领域&#xff0c;为客户提供封装、粘合、散热、装配制造等…

拿下宁王、迪王的湖南裕能,还能“狂飙”多远?

文|智能相对论作者|Kinki近日&#xff0c;磷酸铁锂正极材料龙头湖南裕能正式登陆A股&#xff0c;上市当天市值超过了400亿元&#xff0c;投资者中一签可赚1.49万元&#xff0c;可谓近年低迷的资本市场中一支“大肉签”。不过在 “开门红”之后&#xff0c;湖南裕能的股价便一路…

leetcode16. 最接近的三数之和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。 返回这三个数的和。 假定每组输入只存在恰好一个解。 示例 1&#xff1a; 输入&#xff1a;nums [-1,2,1,-4], target 1 输出&#xff1a;2 …

linux服务器上Docker中安装jenkins

前言 Jenkins是开源CI&CD软件领导者&#xff0c; 提供超过1000个插件来支持构建、部署、自动化&#xff0c; 满足任何项目的需要。 本文主要提供通过docker安装jenkins镜像&#xff0c;并配置nginx反向代理页面配置和使用。通过jenkins完成项目的自动部署。 我在安装之前…

ThinkPHP ^6图片操作进阶

图片裁剪、缩略、水印不再是TP框架系统内置的功能&#xff0c;需要安装。 目录 安装 图片处理 1.创建图片对象 2.获取图片属性 3.裁剪图像 4.生成缩略图 6.保存图像 7.水印 安装 使用composer在项目根目录打开命令行执行&#xff1a; composer require topthink/think…

Simulink 电机控制:单电阻三相电流重构算法仿真总结

目录 理论基础 仿真实现 硬件实现 总结 理论基础 参考Microchip AN1299 《PMSM 无传感器 FOC的单分流三相电流重构算法》&#xff0c;详细的理论可以参考这个文档&#xff0c;这里只简单总结一下。单电阻采样的核心就是要在一个周期内实现两相电流的采样并通过基尔霍夫定…