三层限流:为高并发系统保驾护航

news2024/12/24 2:11:31

文章目录

  • 前言
  • 第一层限流:Nginx 层面的 IP 限流
  • 第二层限流:Gateway 对用户层级的限流
  • 第三层限流:微服务限流
    • 分布式限流和单机限流的优缺点:
    • 1、RateLimiter的使用
    • 2、Hystrix的使用
    • 3、Redis+lua脚本
    • 4、使用Sentinel
  • 关于为何同时使用 Nginx 和 Spring Cloud Gateway

前言

在高并发网络环境中,确保系统的可用性、稳定性以及防范恶意流量攻击至关重要。为此,在我们的项目中构建了三层限流设计。

第一层是 Nginx 层面的 IP 限流,借助 Nginx 的 http_limit_req_module 模块,依据用户 IP 设限,这是抵御恶意 IP 的 DDoS 攻击、阻挡大量非法请求深入系统的首道防线。

第二层为 gateway 针对用户层级的限流,通过用户的唯一标识如 user_id,控制每个用户在单位时间内的请求数量,确保公平,避免单一用户过度占用资源而影响他人体验。

第三层则是微服务限流,每个微服务运用如 Google 的 Guava RateLimiter 等技术限流,防止服务过载影响系统稳定,且各服务根据自身能力和业务需求独立设定限流阈值。

三层限流的结构图如下:
三层流程结构

第一层限流:Nginx 层面的 IP 限流

Nginx 的 http_limit_req_module 模块是我们构建的第一道坚固防线。 其工作原理基于定义一个明确的“速率”值,用于对单位时间内的请求数量进行严格的限制。比如说,您可以设定每分钟只处理 100 个请求。当某个客户端的请求速率超过预先设定的限制时,Nginx 就会地将这些请求放入一个专门的队列中,等待后续的处理。然而,如果队列中的请求数量过多,或者等待处理的时间超出了可接受的范围,那么这些请求将会被果断丢弃。

在配置方面,首先需要在 Nginx 的 http 块中使用 limit_req_zone 指令来定义限制速率的区域。例如,如果我们决定根据客户端的 IP 进行限制,限流20MB,每秒允许处理1000个请求,配置如下:

http http {   
 limit_req_zone $binary_remote_addr zone=perip:20m rate=1000r/s; 
 ...
} 

这里,$binary_remote_addr 代表客户端的 IP 地址,zone=totalLimit:20m 定义了一个名为 totalLimit 的存储区域,其大小为 20M,用于存储每个 IP 的状态信息,而 rate=10r/s 则清晰地设定了每秒 1000 个请求的限制速率。

接下来,在需要应用这个限制的 server 块或 location 块中,使用 limit_req 指令来设定这个限制。例如:

json server {    
	location / {        
	limit_req zone=totalLimit burst=1000 nodelay;
    ...
	}
}

​ 在上述配置中,zone=totalLimit 明确表示应用之前定义的 totalLimit 区域,而 burst=1000 则意味着允许在短时间内超过定义的速率,最多累积 1000 个请求等待处理。 通过调整 rateburstnodelay 等配置参数,我们能够根据不同的业务需求灵活定制限流策略。比如,假设我们的业务主要面向个人用户,并且大部分时间请求量相对稳定,但在某些特定的高峰期会出现请求量的突然增加。在这种情况下,我们可能会选择设定一个适中的 rate ,并同时允许一定数量的 burst ,以在保障系统稳定性的同时,最大程度地优化用户体验。

第二层限流:Gateway 对用户层级的限流

为了进一步增强系统的限流效果和精细化管理,我们在 API 网关层面也实施了限流策略。 首先,在 Spring Cloud Gateway 中,通过在配置文件中明确地定义限流规则,我们能够基于 user_id 这一关键标识,精准地控制每个用户在单位时间内所能发送的请求数量。以下是一个针对 user_id 进行限流的配置示例:

spring:
  cloud:
    gateway:
      routes:
      - id: user_route
        uri: http://mybackend.com
        predicates:
        - Path=/api/**
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 10
            redis-rate-limiter.burstCapacity: 20
            key-resolver: "#{@userIdResolver}"

在这个配置中,redis-rate-limiter.replenishRate 定义了每秒可以处理的请求数量,而 redis-rate-limiter.burstCapacity 则设定了可以接受的突发请求数量。同时,key-resolver 用于明确如何从请求中准确获取 user_id

为了实现从请求中提取 user_id ,我们需要实现一个 KeyResolver 接口。以下是一个使用 Java 语言实现的示例代码,展示了如何从请求的查询参数中获取 user_id

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    <version>3.3.1</version>
</dependency>

2、配置获取用户id的方法

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class RateLimiterConfiguration
    
    @Bean
    KeyResolver userIdKeyResolver(){
    	return Mono.just(
            exchange.getRequest().getQueryParams().getFirst("user_id")
        );
	}
}

3、配置c配置文件中的过滤配置

spring:
  cloud:
    gateway:
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1000     # 令牌桶每秒填充平均速率
            redis-rate-limiter.burstCapacity: 2000     # 令牌桶的上限
            key-resolver: "#{@userIdResolver}"        # 使用spel表达式从spring容器中获取Bean对象

网关层面限流的好处众多,通过这样的设计,极大地简化了整个系统的工作流程和管理复杂度。 网关层面实现限流机制带来了诸多显著的好处。

首先,它能够为所有的应用程序提供一个统一的流量控制管理,使得我们能够在一个集中的位置对所有服务的流量进行有效的监管和控制,而无需在每个服务中分别进行复杂的配置。

其次,限流机制能够有效地防止任何一个服务由于过大的流量冲击而陷入崩溃的境地,从而显著增强了系统的稳定性。

再者,它能够有效地对系统中的每个用户的请求进行精确控制,避免了某些用户过度占用系统资源而对其他用户的体验造成不良影响。

最后,对于可预期的高流量请求场景,我们能够在网关层面迅速而灵活地进行调整和应对,保障系统的正常运行。

第三层限流:微服务限流

在每个微服务内部,每个微服务都能够根据自身的处理能力和独特的业务需求,独立设定限流阈值。这种个性化的设置确保了每个微服务在面对不同的负载情况时,都能够保持稳定的性能和可靠的服务质量,从而有效地防止了某个服务的过载对整个系统的稳定性产生不利影响。

在这层中有多种技术来实现微服务限流,可以采用,如 Google 的 Guava RateLimiter、SpringCloud的Hystrix等框架来实施单机限流策略,也可以采用 阿里的Sentinel、或者自己用Redis+lua脚本实现分布式限流对微服务多个实例统一限流。

分布式限流和单机限流的优缺点:

分布式限流的优点:

  1. 全局一致:确保整个系统限流策略统一,维持稳定。
  2. 适应高并发:处理大规模、高流量场景,保障系统稳定。
  3. 弹性扩展:随系统规模变化能灵活调整限流策略。
  4. 精准控制:依据系统整体情况精确限流。

**分布式限流的缺点: **

  1. 复杂:实现和维护难度大,涉及协调和同步问题。

  2. 有性能开销:节点通信带来一定性能损失。

  3. 依赖外部组件:增加系统依赖和故障点。

单机限流的优点:

  1. 简单:实现逻辑简单,无需复杂协调机制。

  2. 低开销:无节点通信,性能影响小。

  3. 独立:限流策略不受其他节点干扰。

单机限流的缺点:

  1. 局限:只能处理单机器流量。
  2. 缺乏协调:不同机器策略难统一,影响系统稳定。
  3. 难扩展:无法直接用于多机环境。

1、RateLimiter的使用

RateLimiter使用起来比较简单,代码示例如下

 public static void main(String[] args) {
        // 创建一个每秒放入5个令牌的RateLimiter
        RateLimiter limiter = RateLimiter.create(100.0);

        for (int i = 0; i < 10; i++) {
            // 请求一个令牌
            limiter.acquire();
            System.out.println("处理请求: " + i);
        }
    }

这段代码创建了一个RateLimiter,它每秒产生100个令牌。在一个循环中,我们通过acquire()方法从RateLimiter获取令牌。如果令牌不够,acquire()会阻塞,直到获取到令牌

2、Hystrix的使用

Hystrix的限流是基于线程池的所以在配置文件里设置hystrix线程池的核心线程数就可实现限流

hystrix:
  threadpool:
    default:
      coreSize: 200 #并发执行的最大线程数,默认10
      maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
      queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5

3、Redis+lua脚本

limit.lua

local count
- 获取调用脚本时传入的第一个key值(用作限流的 key)
count = redis.call('get',KEYS[1])
-- 获取调用脚本时传入的第一个参数值(限流大小)
if count and tonumber(count) > tonumber(ARGV[1]) then
    return count;
end
    count = redis.call('incr',KEYS[1])
if tonumber(count) == 1 then
    --从第一次调用开始限流,设置对应key的过期时间
    redis.call('expire',KEYS[1],ARGV[2])
end
return count;

大致的代码逻辑,实际使用建议封装成一个注解来使用

	DefaultRedisScript<Number> redisScript = new DefaultRedisScript<>();
    redisScript.setResultType(Number.class);
    ClassPathResource classPathResource = new ClassPathResource(LIMIT_LUA_PATH);
    try {
        classPathResource.getInputStream();//探测资源是否存在
        redisScript.setScriptSource(new ResourceScriptSource(classPathResource));
    } catch (IOException e) {
        logger.error("未找到文件:{}", LIMIT_LUA_PATH);
    }	
	List result = stringRedisTemplate.execute(redisScript, keyList, 					String.valueOf(value),String.valueOf(time));
 


		Object result = stringRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
 		if (result == null) {
            //降级
        } 
		Integer count = Integer.valueOf(result.toString());
        if (count <= limitCount) {
            //执行业务逻辑
        } else {
            //降级
        }

4、使用Sentinel

Sentinel 是阿里出品的一个功能强大的分布式限流框架,具体使用可以参考下面的官方文档

quick-start | Sentinel (sentinelguard.io)

关于为何同时使用 Nginx 和 Spring Cloud Gateway

首先,我们的项目主要基于 Spring Boot 和 Spring Cloud 进行开发,而 Spring Cloud Gateway 能够与这些技术实现无缝的集成。这种紧密的集成特性不仅减少了我们在处理不同组件之间兼容性问题上所花费的时间和精力,还极大地提高了开发和维护的效率。

其次,Spring Cloud Gateway 支持非阻塞的方式来处理请求,这一特性在处理高并发请求时表现出了显著的优势,是 Nginx 所无法提供的。非阻塞的处理方式能够更高效地利用系统资源,提升系统的整体性能和响应速度。

再者,Spring Cloud Gateway 允许我们通过动态的编程方式来定义路由规则,与 Nginx 相对静态的配置方式形成了鲜明的对比。这种动态定义路由规则的能力使我们能够更加灵活地应对复杂多变的业务需求和系统架构调整,为系统的持续演进提供了有力的支持。

最后,Spring Cloud Gateway 还集成了 Spring Cloud 的服务发现功能,以及与 Spring Cloud 集群紧密配合的断路、降级和限流等机制。这些集成的功能共同强化了微服务架构的健壮性和可靠性,确保系统在面对各种异常情况和高负载场景时,依然能够保持稳定的运行状态,为用户提供持续、优质的服务。

综上所述,虽然 Nginx 已经在网络服务领域展现出了强大的实力,但在我们特定的项目架构中,Spring Cloud Gateway 凭借其与现有技术栈的高度适配性、独特的功能特性以及对微服务架构的全面支持,更加符合我们的项目需求。

综上所述,这三层限流设计相互协作、相辅相成,从不同的层面和角度全方位地保障了系统在高并发场景下的稳定运行。

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

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

相关文章

力扣:LCR 024. 反转链表(Java)

目录 题目描述&#xff1a;示例 1&#xff1a;示例 2&#xff1a;代码实现&#xff1a; 题目描述&#xff1a; 给定单链表的头节点 head &#xff0c;请反转链表&#xff0c;并返回反转后的链表的头节点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#x…

Python实现接糖果小游戏

介绍: 基于Pygame的糖果从屏幕顶部下落的游戏代码。这个游戏包括了一个可以左右移动的篮子来接住下落的糖果&#xff0c;接住糖果会增加得分。 代码: import pygame import random import os# 初始化pygame和设置屏幕大小 pygame.init() screen_width, screen_height 800, 6…

抖音矩阵云混剪系统源码 短视频矩阵营销系统V2(全开源版)

>>>系统简述&#xff1a; 抖音阵营销系统多平台多账号一站式管理&#xff0c;一键发布作品。智能标题&#xff0c;关键词优化&#xff0c;排名查询&#xff0c;混剪生成原创视频&#xff0c;账号分组&#xff0c;意向客户自动采集&#xff0c;智能回复&#xff0c;多…

Python基础小知识问答系列-可迭代型变量赋值

1. 问题&#xff1a; 怎样简洁的把列表中的元素赋值给单个变量&#xff1f; 当需要列表中指定几个值时&#xff0c;剩余的变量都收集在一起&#xff0c;该怎么进行变量赋值&#xff1f; 当只需要列表中指定某几个值&#xff0c;其他值都忽略时&#xff0c;该怎么…

拓扑排序[讲课留档]

拓扑排序 拓扑排序要解决的问题是给一个有向无环图的所有节点排序。 即在 A O E AOE AOE网中找关键路径。 前置芝士&#xff01; 有向图&#xff1a;有向图中的每一个边都是有向边&#xff0c;即其中的每一个元素都是有序二元组。在一条有向边 ( u , v ) (u,v) (u,v)中&…

数字营销执行团队的重要性及如何配置

在当今数字化商业环境中&#xff0c;数字营销执行团队的重要性不言而喻。 其重要性主要体现在以下几点&#xff1a;一是能精准触达目标客户&#xff0c;借助数据分析实现精准营销&#xff0c;提高营销效率和效果。二是紧跟数字技术发展潮流&#xff0c;及时采用新的营销手段和…

基于OpenCV与Keras的停车场车位自动识别系统

本项目旨在利用计算机视觉技术和深度学习算法&#xff0c;实现对停车场车位状态的实时自动识别。通过摄像头监控停车场内部&#xff0c;系统能够高效准确地辨认车位是否被占用&#xff0c;为车主提供实时的空闲车位信息&#xff0c;同时为停车场管理者提供智能化的车位管理工具…

【JVM-04】线上CPU100%

【JVM-04】线上CPU100% 1. 如何排查 1. 如何排查 ⼀般CPU100%疯狂GC&#xff0c;都是死循环的锅&#xff0c;那怎么排查呢&#xff1f;先进服务器&#xff0c;⽤top -c 命令找出当前进程的运⾏列表按⼀下 P 可以按照CPU使⽤率进⾏排序显示Java进程 PID 为 2609 的java进程消耗…

清新简约卡片风格蓝紫渐变色短视频苹果CMS模板

首途第三十三套清新简约卡片风格蓝紫渐变色短视频模板&#xff0c;一套苹果CMSV10主题。 这套主题是简约风格&#xff0c;以纯洁的白色和深邃的紫色为主色调&#xff0c;为您提供了一种清新、时尚的浏览体验。 在这个简洁而美丽的界面中&#xff0c;您可以轻松畅享各种精彩短…

【微服务】微服务之Feign 与 Ribbon

文章目录 强烈推荐引言优点Feign示例什么是Ribbon&#xff1f;Ribbon 的优点Netflix Feign 和 Ribbon整合Feign 与 Ribbon 的关系Feign 与 Ribbon 结合使用的示例配置文件&#xff08;application.yml&#xff09;说明&#xff1a; Feign 与 Ribbon 结合使用的应用场景1. 动态服…

Win10系统登录界面两个相同用户名的处理方法

在Windows 10系统中只设置了一个帐户&#xff0c;即本地帐户Administrator&#xff0c;但由于一些应用对帐户的要求&#xff0c;会切换到微软帐户登录&#xff0c;而切换之后&#xff0c;在登录界面就会显示微软帐户的名称&#xff0c;故Windows 10系统在登录界面会显示两个相同…

windows网络进阶之listen参数含义

目录 一、前言 二、listen参数 三、实战案例 1. 使用Sleep函数让线程暂停 2. 案例代码展示 3. 编译生成exe文件 一、前言 一、前言 在Windows网络编程中&#xff0c;在使用TCP时&#xff0c;listen函数它是一个重要的关键的步骤&#xff0c;使得套接字能够接收传…

【C++】使用C++在线程中动态记录数据到外部文件

在现代软件开发中&#xff0c;多线程编程已成为处理并发任务、提高程序性能的重要手段。而在多线程环境下&#xff0c;如何有效地管理和记录数据&#xff0c;尤其是将动态生成的数据安全地写入外部文件&#xff0c;是许多应用程序必须面对的问题。本文将深入探讨如何在C中使用多…

FastApi中的常见请求类型

FastApi中的常见请求类型 后端开发语言中&#xff0c;我钟情于node&#xff0c;高效的异步处理真是让我眼前一亮&#xff0c;同时&#xff0c;简单易懂的语法也让我非常倾心 但是但是&#xff0c;因为考虑要写一个深度学习算法的后端接口&#xff0c;所以不得不选用python作为…

传神论文中心|第15期人工智能领域论文推荐

在人工智能领域的快速发展中&#xff0c;我们不断看到令人振奋的技术进步和创新。近期&#xff0c;开放传神&#xff08;OpenCSG&#xff09;社区发现了一些值得关注的成就。传神社区本周也为对AI和大模型感兴趣的读者们提供了一些值得一读的研究工作的简要概述以及它们各自的论…

【linux】虚拟机安装 BCLinux-R8-U4-Server-x86_64

目录 一、概述 1.1移动云Linux系统订阅服务 CLS 1.2 大云天元操作系统BC-Linux 二、安装 一、概述 1.1移动云Linux系统订阅服务 CLS 移动云Linux系统订阅服务 CLS &#xff08;Cloud Linux Service&#xff09;为使用BC-Linux操作系统的用户提供标准维保服务以及高级技术支…

JVM原理(九):JVM虚拟机工具之可视化故障处理工具

1. JHSDB:基于服务性代理的调试工具 JHSDB是一款基于服务性代理实现的进程外调试工具。 服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的、主要基于Java语言实现的API集合。 2. JConsole:Java监视与管理控制台 JConsole是一款基于JMX的可视化监视、管理工具。…

矩阵、混剪、大盘,3大功能升级优化!助力企业高效管理!

在数字化转型的浪潮中&#xff0c;企业对于工具与技术的需求愈发强烈。 为满足市场需求&#xff0c;本月【云略】为各企业上线了便捷功能&#xff0c;赋能企业经营决策和业务增长。 矩阵管理 √【矩阵号管理】抖音支持设置城市IP 内容管理 √【混剪任务】支持关联智能发布计…

模拟 ADC 的前端

ADC 的 SPICE 模拟 反复试验的方法将信号发送到 ADC 非常耗时&#xff0c;而且可能有效也可能无效。如果转换器捕获电压信息的关键时刻模拟输入引脚不稳定&#xff0c;则无法获得正确的输出数据。SPICE 模型允许您执行的步是验证所有模拟输入是否稳定&#xff0c;以便没有错误…

百家讲坛 | 裴伟伟:企业中安全团队应当如何反馈漏洞

作者简介&#xff1a;裴伟伟&#xff0c;洞源实验室创始人&#xff0c;国家网安基地网络安全行业专家&#xff0c;网安加社区特聘专家&#xff0c;持有CISSP、PMP证书&#xff0c;曾在HITCON、可信云大会、开源产业大会等安全论坛发表演讲。曾任国内某安全实验室负责人、某互金…