限流是什么?如何限流?怎么限流?

概述

什么是限流

某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机

为什么要限流

因为互联网系统通常都要面对大并发大流量的请求,在突发情况下(最常见的场景就是秒杀、抢购),瞬时大流量会直接将系统打垮,无法对外提供服务。那为了防止出现这种情况最常见的解决方案之一就是限流

限流维度&分类

维度

两种维度:

  1. 时间:基于某段时间范围或者某个时间点,也就是我们常说的“时间窗口”,比如对每分钟、每秒钟的时间窗口做限定
  2. 资源:基于可用资源的限制,比如设定最大访问次数,或最高可用连接数

总结:限流就是在某个时间窗口对资源访问做限制,但在真正的场景里,我们不止设置一种限流规则,而是会设置多个限流规则共同作用

分类

限流的分类如下所示:

  1. 合法性验证限流:比如验证码、IP 黑名单等,这些手段可以有效的防止恶意攻击和爬虫采集;
  2. 容器限流:比如 Tomcat、Nginx 等限流手段,
    1. 其中 Tomcat 可以设置最大线程数(maxThreads),当并发超过最大线程数会排队等待执行;
    2. 而 Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数;
  3. 服务端限流:比如我们在服务器端通过限流算法实现限流

限流指标(TPS、HPS、QPS、RPS)

TPS(Transactions Per Second) 是指每秒事务数:一个事务是指事务内第一个请求发送到接收到最后一个请求的响应的过程,以此来计算使用的时间和完成的事务个数。

  • 如果按照TPS来进行限流,时间粒度可能会很大大,很难准确评估系统的响应性能。

HPS(Hits Per Second)每秒点击次数(每秒钟服务端收到客户端的请求数量) 。是指在一秒钟的时间内用户对Web页面的链接、提交按钮等点击总和。 它一般和TPS成正比关系,是B/S系统中非常重要的性能指标之一。

  • 如果一个请求完成一笔事务,那TPS和HPS是等同的
  • 但在分布式场景下,完成一笔事务可能需要多次请求,所以TPS和HPS指标不能等同看待。

QPS(Queries Per Second) 是指每秒查询率。是一台服务器每秒能够响应的查询次数(数据库中的每秒执行查询sql的次数),显然这个不够全面,不能描述增删改,所以不建议用QPS来作为系统性能指标

  • 如果后台只有一台服务器,那 HPS 和 QPS 是等同的。
  • 但是在分布式场景下,每个请求需要多个服务器配合完成响应。

RPS(Requests Per Second) 是一个衡量系统或应用程序在一秒钟内能够处理的请求数量的性能指标。

容器限流

有两种限流方式:

  1. tomcat限流:配置最大线程数
  2. nginx限流:一是控制速率,二是控制并发连接数

tomcat限流

spring项目直接可以在配置文件中设置

  • 使用 application.properties:src/main/resources/application.properties文件中添加以下配置
    server.tomcat.max-threads=200  # 设置最大线程数为 200
    
  • 使用 application.yml:在 src/main/resources/application.yml 中添加:
    server:
      tomcat:
        max-threads: 200  # 设置最大线程数为 200
    

nginx限流

控制速率:

  • 使用limit_req_zone用来限制单位时间内的请求数,即速率限制,
    • 示例配置如下:
    • 配置表示,限制每个 IP 访问的速度为 2r/s,因为 Nginx 的限流统计是基于毫秒的,我们设置的速度是 2r/s,转换一下就是 500ms 内单个 IP 只允许通过 1 个请求,从 501ms 开始才允许通过第 2 个请求
      limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
      server { 
          location / { 
              limit_req zone=mylimit;
          }
      }
      
  • burst 关键字:真实情况下我们应该控制一个 IP 单位总时间内的总访问次数,而不是像上面那么精确但毫秒,我们可以使用 burst 关键字开启此设置,它表示在限速时,允许的额外请求数量
  • 示例配置如下:
  • burst=4 表示每个 IP 最多允许4个突发请求
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
    server { 
        location / { 
            limit_req zone=mylimit burst=4;
        }
    }
    

控制并发数

  • 利用 limit_conn_zone 和 limit_conn 两个指令即可控制并发数
    • 示例配置如下:
    • 其中 limit_conn perip 10 表示限制单个 IP 同时最多能持有 10 个连接
      • 只有当 request header 被后端处理后,这个连接才进行计数。
    • limit_conn perserver 100 表示 server 同时能处理并发连接的总数为 100 个
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

服务端限流算法

常见的限流算法有三种:

  • 计数器限流:主要用来限制总并发数,比如数据库连接池大小、线程池大小、接口访问并发数等都是使用计数器算法
  • 漏桶算法
    • 思路:漏桶算法思路很简单,我们把水比作是请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流
  • 令牌桶算法:可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病
    • 系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。
    • 令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制
    • 漏桶算法区别
      • 漏桶的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。
      • 令牌桶则不同,其特性可以“预存”一定量的令牌,因此在应对突发流量的时候可以在短时间消耗所有令牌,其突发流量处理效率会比漏桶高,导向后台系统的压力也会相应增多
  • 滑动窗口

计数器限流(固定窗口算法)

计数器限流算法也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。也是最简单粗暴的算法。
常用的三个方法如下:

  1. 采用AtomicInteger使用AomicInteger来进行统计当前正在并发执行的次数,如果超过域值就简单粗暴的直接响应给用户,说明系统繁忙,请稍后再试或其它跟业务相关的信息。
    • 弊端:使用 AomicInteger 简单粗暴超过域值就拒绝请求,可能只是瞬时的请求量高,也会拒绝请求
  2. 采用令牌Semaphore:使用Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求
    • 相对Atomic优点:如果是瞬时的高并发,可以使请求在阻塞队列中排队,而不是马上拒绝请求,从而达到一个流量削峰的目的。
  3. 采用ThreadPoolExecutor java线程池固定线程池大小,超出固定先线程池和最大的线程数,拒绝线程请求

滑动窗口

滑动窗口算法是对固定窗口算法的改进

滑动窗口计数器(Sliding Window)算法限流:解决固定窗口临界值的问题。它将单位时间周期分为n个小周期,分别记录每个小周期内接口的访问次数,并且根据时间滑动删除过期的小周期

如下图

  • 每 500ms 滑动一次窗口,可以发现窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小,
  • 不过只要有时间窗口的存在,还是有可能发生时间窗口的临界突变问题。
    sss

漏桶算法

漏桶的桶有大小,就如队列的容量,当请求堆积超过指定容量时,会触发拒绝策略。
漏桶模式中的消费处理总是能以恒定的速度进行,可以很好的保护自身系统不被突如其来的流量冲垮
在这里插入图片描述

令牌桶算法

最为常用的 Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶的实现思路类似于生产者和消费之间的关系
在这里插入图片描述

系统服务作为生产者,按照指定频率向桶(容器)中添加令牌,如 QPS 为 2,每 500ms 向桶中添加一个令牌,如果桶中令牌数量达到阈值,则不再添加

  • 1s / 阈值(QPS) = 令牌添加时间间隔。

请求执行作为消费者,每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的

实现方式

算法

应用级限流方式只是单应用内的请求限流,不能进行全局限流。

  1. 限流总资源数
  2. 限流总并发/连接/请求数
  3. 限流某个接口的总并发/请求数
  4. 限流某个接口的时间窗请求数
  5. 平滑限流某个接口的请求数
  6. Guava RateLimiter

我们需要分布式限流和接入层限流来进行全局限流。

  1. redis+lua实现中的lua脚本
  2. 使用Nginx+Lua实现的Lua脚本
  3. 使用 OpenResty 开源的限流方案
  4. 限流框架,比如Sentinel实现降级限流熔断

单点限流

应用级限流方式只是单应用内的请求限流,不能进行全局限流。

  1. 限流总资源数
  2. 限流总并发/连接/请求数
  3. 限流某个接口的总并发/请求数
  4. 限流某个接口的时间窗请求数
  5. 平滑限流某个接口的请求数
  6. Guava RateLimiter
Guava实现限流(令牌桶Token Bucket)

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法实现流量限制

依赖包
  • 版本选择:请根据你的项目需求选择合适的Guava版本。最新版本可以在Guava的Maven中央仓库页面上找到。
  • 兼容性:确保所选版本与项目中使用的Java版本兼容
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
RateLimiter 常用方法

acquire:获取令牌

  • acquire() :获取一个令牌, 该方法会阻塞直到获取到这一个令牌, 返回值为获取到这个令牌花费的时间
  • acquire(int permits) :获取指定数量的令牌, 该方法也会阻塞, 返回值为获取到这 N 个令牌花费的时间

tryAcquire:当前能否获取到令牌

  • tryAcquire() :判断当前时候能获取到令牌, 如果不能获取立即返回 false
  • tryAcquire(int permits) :判断当前时候指定数量的令牌, 如果不能获取立即返回 false
  • tryAcquire(long timeout, TimeUnit unit) :判断能否在指定时间内获取到令牌, 如果不能获取立即返回 false
  • tryAcquire(int permits, long timeout, TimeUnit unit) :判断能否在指定时间内获取到指定数量的令牌, 如果不能获取立即返回 false
具体使用
示例1:直接使用RateLimiter

创建限流配置

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;

@Component
public class RateLimitService {
   
    // 每秒允许5个请求
    private final RateLimiter rateLimiter = RateLimiter.create(5.0);

    public boolean tryAcquire() {
   
        return rateLimiter.tryAcquire();
    }
}

给接口加上限流逻辑
在Controller中,使用RateLimitService来限制接口的访问。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
   

    @Autowired
    private RateLimitService rateLimitService;

    @GetMapping("/api/resource")
    public ResponseEntity<String> getResource() {
   
        if (!rateLimitService.tryAcquire()) {
   
            return ResponseEntity.status(429).body("Too Many Requests");
        }
        // 处理请求
        return ResponseEntity.ok("Resource accessed successfully");
    }
}

我们在实际开发中并不能直接这样用

  • 每个接口都需要手动给其加上tryAcquire(),业务代码和限流代码混在一起,而且明显违背了DRY原则,代码冗余,重复劳动
  • 因此需要使用下面的方式去获取:自定义注解 + AOP
示例2:自定义注解(RateLimiter) + AOP

上述方式使用RateLimiter的方式不够优雅,尽管我们可以把RateLimiter的逻辑包在service里面,controller直接调用即可,但是如果我们换成:自定义注解+切面 的方式实现的话,会优雅的多
自定义注解:如何自定义注解?

import java.lang.annotation.*;
 
/**
 * 自定义注解可以不包含属性,成为一个标识注解
 */
@Inherited
@Documented
@Target({
   ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAspect {
   
    /**
     * 资源的key,唯一
     * 作用:不同的接口,不同的流量控制
     */
    String key() default "";

    /**
     * 最多的访问限制次数
     */
    double permitsPerSecond () ;

    /**
     * 获取令牌最大等待时间
     */
    long timeout();

    /**
     * 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
     */
    TimeUnit timeunit() default TimeUnit.MILLISECONDS;

    /**
     * 得不到令牌的提示语
     */
    String msg() default "系统繁忙,请稍后再试.";
}

自定义切面类

import com.google.common.util.concurrent.RateLimiter;
import net.sf.json.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang

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

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

相关文章

DS堆的实际应用(10)

文章目录 前言一、堆排序建堆排序 二、TopK问题原理实战创建一个有一万个数的文件读取文件并将前k个数据创建小堆用剩余的N-K个元素依次与堆顶元素来比较将前k个数据打印出来并关闭文件 测试 三、堆的相关习题总结 前言 学完了堆这个数据结构的概念和特性后&#xff0c;我们来看…

DVWA | Files Upload(文件上传)通关笔记

概念 **文件上传漏洞**是网络安全中常见的漏洞之一&#xff0c;攻击者可以利用该漏洞上传恶意文件&#xff0c;进而在服务器上执行恶意代码、绕过权限验证或获取敏感数据。文件上传漏洞主要发生在允许用户上传文件的Web应用程序中&#xff0c;比如图像、文档上传功能等。 ###…

dayjs日期格式化,开发uniapp或unicloud前后端进行时间格式转换

一、 为什么要用日期格式化 因为在开发项目过程中&#xff0c;会遇到各种各样的日期格式&#xff0c;有的显示完整的年-月-日 时:分:秒&#xff0c;而有的场景就只显示月-日等格式&#xff0c;还有就是显示当前时间和注册时间的间隔时长等&#xff0c;场景非常多&#xff0c;如…

学习 Flutter 的最佳路线图

学习 Flutter 的最佳路线图 视频 https://youtu.be/IpKXVq9lP_4 https://www.bilibili.com/video/BV1J92uYDEit/ 前言 原文 Flutter 开发者必看&#xff1a;全面的学习路线图 本文借鉴了 roadmap 的思路&#xff0c;为大家介绍如何有效学习 Flutter。 该路线图提供了从零开…

MySQL-DQL练习题

文章目录 简介初始化表练习题 简介 本节简介: 主要是一些给出一些习题, 关于DQL查询相关的, DQL查询语句是最重要的SQL语句, 功能性最复杂, 功能也最强, 所以本节建议适合以及有了DQL查询基础的食用, 另外注意我们使用的是Navicat, SQL编辑的格式规范也是Navicat指定的默认格式…

uni-app uni.setTabBarBadge 不生效

‘text’属性&#xff0c;类型必须是字符串&#xff0c;而接口返回的是数值&#xff0c;没有注意到&#xff0c;所以怎么都不生效&#xff0c;也不会有报错&#xff01;

基于一个python库tencent的API接口开发有趣应用

这篇博客给大家介绍一个python库 tencent (https://pypi.org/project/tencent/) 以及对应三方API的开发流程&#xff0c;以公众号后台通过服务器接入自动系统回复为例。基于微信公众号后台开发自动回复&#xff0c;或者利用多模态信息回复用户输入&#xff0c;需要自己有独立服…

python爬虫实战案例——从移动端接口抓取微博评论,采用cookie登陆,数据存入excel表格,超详细(15)

文章目录 1、任务目标2、网页分析3、代码编写3.1 代码分析3.2 完整代码1、任务目标 1、目标网站:微博文章(https://m.weibo.cn/detail/4813628149072458),这是微博某一篇博文,用于本文测试 2、要求:爬取该博文下,所有一级评论和二级评论,以及每条评论的作者,最后保存至E…

【Kafka】Kafka源码解析之producer过程解读

从本篇开始 打算用三篇文章 分别介绍下Producer生产消费&#xff0c;Consumer消费消息 以及Spring是如何集成Kafka 三部分&#xff0c;致于对于Broker的源码解析&#xff0c;因为是scala语言写的&#xff0c;暂时不打算进行学习分享。 总体介绍 clients : 保存的是Kafka客户端…

Docker新手必看:快速安装和配置BookStack在线文档系统

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动BookStack容器4. 登录与简单使用5. 公网远程访问本地BookStack5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 前言 本文主要介绍如何在Linux系统使用Docker本地部署在线文档管理…

基于SSM医药垃圾分类管理系统【附源码】

基于SSM医药垃圾分类管理系统 效果如下&#xff1a; 系统登录界面 管理员主界面 公告信息管理界面 垃圾分类管理界面 医院垃圾信息管理界面 用户主界面 留言反馈管理界面 研究背景 随着科学技术发展&#xff0c;计算机已成为人们生活中必不可少的生活办公工具&#xff0c;在…

Java语言-抽象类

目录 1.抽象类概念 2.抽象类语法 3.抽象类特性 4.抽象类作用 1.抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c; 如果 一个类中没有包含足够的信息来描绘一个具体…

初阶数据结构【2】--顺序表(详细且通俗易懂,不看一下吗?)

本章概述 线性表顺序表顺序表问题与思考彩蛋时刻&#xff01;&#xff01;&#xff01; 线性表 概念&#xff1a;一些在逻辑上成线性关系的数据结构的集合。线性表在逻辑上一定成线性结构&#xff0c;在物理层面上不一定成线性结构。常见的线性表&#xff1a;顺序表&#xff0…

ICT产业新征程:深度融合与高质量发展

在信息时代的浪潮中&#xff0c;每一场关于技术革新与产业融合的盛会都闪耀着智慧的光芒&#xff0c;引领着未来的方向。9月25日&#xff0c;北京国家会议中心内&#xff0c;一场聚焦全球信息通信业的顶级盛事——第32届“国际信息通信展”&#xff08;PT展&#xff09;隆重拉开…

C++新手入门指南:从基础概念到实践之路

C 继承了 C 语言的高效性和灵活性&#xff0c;同时新增了面向对象编程的特点。这使得 C 既可以进行底层系统编程&#xff0c;又能进行面向对象的软件设计。在面向对象编程方面&#xff0c;C 支持封装、继承和多态三大特性。 &#x1f4af;C 初印象 语言的发展就像是练功打怪…

【Docker】Docker基本操作

目录 一、了解云计算背景 1.1 云计算的三种服务模式 1.2 虚拟机的两种架构 二、Docker 概述 2.1 Docker简述 2.2 Docker 特点 2.3 Docker与虚拟机的区别 2.4 容器技术有哪些 2.4.1 namespace的六项隔离 2.5 Docker核心概念 2.5.1 镜像 2.5.2 容器 2.5.3 仓库 三、…

吴恩达深度学习笔记(6)

正交化 为了提高算法准确率&#xff0c;我们想到的方法 收集更多的训练数据增强样本多样性使用梯度下降将算法使算法训练时间更长换一种优化算法更复杂或者更简单的神经网络利用dropout 或者L2正则化改变网络框架更换激活函数改变隐藏单元个数 为了使有监督机制的学习系统良…

笔试强训10.17

//法一&#xff1a;中点扩散 //法二&#xff1a;动态规划 //法三&#xff1a;hash二分 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N1e610; const int base131; ull hr[2*N],hl[2*N],p[2*N];//超过ull自动取余 char s[N*2];…

如何优化批处理策略,最大限度地“压榨”GPU性能

新手数据科学家和机器学习工程师常常会问一个关键问题&#xff1a;如何判断他们的深度学习训练过程是否在正常运行&#xff1f;在本文中&#xff0c;我们将学习如何诊断和优化深度学习的性能问题&#xff0c;不论是在单台机器还是多台机器上进行训练。通过这些方法&#xff0c;…

uniapp onPageScroll

子组件有onPageScroll, 首页也要引入onPageScroll, eg: 主页面 sell/detail/index 《子组件》 <script setup> 引入onPageScroll </script> 组件&#xff1a; 引入onPageScroll 别人的比较