SaaS 电商设计 (十一) 那些高并发电商系统的限流方案设计

目录

  • 一.什么是限流
  • 二.怎么做限流呢
    • 2.1 有哪些常见的系统限流算法
      • 2.1.1 固定窗口
      • 2.1.1 滑动窗口
      • 2.1.2 令牌桶
      • 2.1.3 漏桶算法
    • 2.2 常见的限流方式
      • 2.2.1 单机限流&集群限流
      • 2.2.2 前置限流&后置限流
    • 2.3 实际落地是怎么做的
      • 2.3.1 流量链路
      • 2.3.2 各链路限流
        • 2.3.2.1 网关层
        • 2.3.2.2 nginx 层
        • 2.3.3.3 服务本身的限流

一.什么是限流

应用系统在自身有限资源下,在面对流量突发场景下的自我保护.这可能是最初的一个目标.在此之上,怎么在有限的资源下去完成系统的最好的性能表现.
那么总结下其实有两个原则.

- 系统保护(主要目标)
- 有限资源下的系统最佳吞吐(理想目标)

有了这两个原则,那么其实我们后续关注的就是如何友好的落地这些点.

二.怎么做限流呢

2.1 有哪些常见的系统限流算法

2.1.1 固定窗口

描述:这是一种简单的限流算法,它根据一个固定的时间窗口来控制流量.在给定时间窗口内,它允许固定数量的请求通过,超出的请求将被丢弃.

实现步骤:

  • 初始化一个计数器(counter)为0,并设置一个固定的时间窗口(window).
  • 当收到一个请求时,检查计数器的值.
  • 如果计数器小于限制的请求数量(limit),则允许请求通过并增加计数器的值.
  • 如果计数器已经达到限制的请求数量,丢弃请求或者返回一个错误.
  • 如果请求的时间超过了时间窗口的结束时间,重置计数器为1,并更新时间窗口为当前时间窗口.
limit = 100  # 限制的请求数量
window = 60  # 时间窗口为60秒

def handle_request():
    global counter, window_start_time
    
    //开始时间
    current_time = time.time()
    
    //60 秒的窗口
    if current_time > window_start_time + window:
        counter = 0
        //重置开始时间
        window_start_time = current_time
    
    if counter < limit:
        counter += 1
        # 处理请求
    else:
        # 丢弃请求或返回错误

相比之下 固定窗口算法的问题在于,无法应对在流量在突发场景下的冲击.在固定窗口下.el:10:00:00 - 10:00:01 时间窗口 60s. 限流值为 1w.
现实过程可能是如下这种场景.
在这里插入图片描述

这种场景对于固定窗口算法来说,造成的问题就是大量的请求都集中在固定窗口的某一段小窗口时间,导致窗口的 pass count 快速被消耗.这样即使后续的平滑流量甚至小
流量都难以继续通过窗口.

2.1.1 滑动窗口

描述:这是一种基于固定窗口算法优化之后的限流算法,在固定窗口算法的基础上引入了滑动窗口的概念.它允许窗口内的请求速率平滑地变化,而不仅仅是在固定窗口内进行限制.

实现步骤:

  • 初始化一个计数器(counter)为0,并设置一个滑动窗口的时间长度(window)和一个固定时间间隔(interval).
  • 当收到一个请求时,检查计数器的值.
  • 如果计数器小于限制的请求数量(limit),则允许请求通过并增加计数器的值.
  • 如果计数器已经达到限制的请求数量,丢弃请求或者返回一个错误.
  • 每隔固定时间间隔,将计数器的值减去窗口内的请求数量,并更新窗口内的请求数量.
limit = 100  # 限制的请求数量
window = 60  # 滑动窗口的时间长度为60秒
interval = 10  # 固定时间间隔为10秒

def handle_request():
    global counter, window_start_time, window_requests
    
    current_time = time.time()
    
    if current_time > window_start_time + window:
        counter -= window_requests.pop(0)
        window_requests.append(0)
        window_start_time = current_time
    
    if counter < limit:
        counter += 1
        window_requests[-1] += 1
        # 处理请求
    else:
        # 丢弃请求或返回错误

2.1.2 令牌桶

描述:原理是系统以恒定的速率产生令牌,并将这些令牌放入一个桶中.当有请求到达时,需要从桶中获取一个令牌才能继续处理该请求.如果桶中没有足够的令牌,则请求被拒绝.

特点:既能够面对突发的流量峰值,也能处理平滑的系统请求.

具体来说,令牌桶算法的实现包括两个关键参数:令牌生成速率(rate)和桶的容量(capacity).令牌生成速率确定了每秒产生的令牌数量,桶的容量确定了桶中最多可以存放多少个令牌.

import time

class TokenBucket:
    def __init__(self, rate, capacity):
        self.rate = rate  # 令牌生成速率(令牌/秒)
        self.capacity = capacity  # 桶的容量(令牌数量)
        self.tokens = 0  # 当前桶中的令牌数量
        self.last_time = time.time()  # 上次令牌生成时间
    
    def get_token(self):
        now = time.time()
        elapsed = now - self.last_time  # 计算时间间隔
        self.last_time = now
        self.tokens = min(self.tokens + elapsed * self.rate, self.capacity)  # 生成令牌
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        else:
            return False

# 示例用法
bucket = TokenBucket(rate=1, capacity=5)  # 令牌生成速率为1个/秒,桶的容量为5个令牌
for i in range(10):
    if bucket.get_token():
        print(f"请求 {i+1} 被处理")
    else:
        print(f"请求 {i+1} 被限流")
        time.sleep(1)  # 等待1秒再重新尝试获取令牌

2.1.3 漏桶算法

漏桶算法(Leaky Bucket Algorithm)是一种流量控制算法,用于平滑网络流量.它模拟了一个漏桶,当网络流量超过桶的容量时,多余的流量会被丢弃或者缓存,以保持流量的稳定性.

漏桶算法的原理是,维护一个固定容量的漏桶,每次流量到达时,先将流量放入漏桶中.如果漏桶已满,则丢弃超出容量的流量;如果漏桶未满,则将流量处理完成.然后,漏桶以固定速率漏水,即以固定速度释放流量.这样,即使流量突然增大,漏桶也可以平滑地处理流量.

class LeakyBucket:
    def __init__(self, capacity, rate):
        self.capacity = capacity  # 漏桶容量
        self.rate = rate  # 漏水速率
        self.water = 0  # 当前水量
        self.last_leak_time = time.time()  # 上次漏水时间

    def process(self, amount):
        current_time = time.time()
        time_passed = current_time - self.last_leak_time
        self.water = max(0, self.water - self.rate * time_passed)  # 漏水

        if self.water + amount <= self.capacity:
            self.water += amount
            self.last_leak_time = current_time
            return True  # 流量处理成功
        else:
            return False  # 漏桶已满,丢弃流量

令牌桶算法更适合平滑限制流量,可以灵活地处理不同请求的流量需求;而漏桶算法更适合于固定速率的流量处理,可以有效地限制请求的处理速度 .对应到实际项目过程中,
漏桶的特点的就是恒定流速,令牌的特点就是可以应对突击的流量,应对秒杀大促等的场景.

2.2 常见的限流方式

2.2.1 单机限流&集群限流

单机限流和集群限流是常见的流量控制方式,它们在实现和应用上有一些区别,并且各自具有不同的优缺点.下面是单机限流和集群限流的区别以及它们的优缺点:

  • 区别:

单机限流:在单个服务器或节点上进行限流,仅对该节点的流量进行控制.
集群限流:在整个集群或多个服务器上进行限流,可以对整个系统的流量进行控制.

  • 单机限流的优缺点:

    • 优点:

实现简单:单机限流通常只需要在单个节点上实现限流逻辑,不需要进行节点间的数据共享和协调.
高效低延迟:限流操作只涉及当前节点,因此具有较高的效率和低延迟.

  • 缺点:

有限的扩展性:单机限流的处理能力受限于单个节点的性能,当流量过大时,单个节点可能无法承受.

  • 集群限流的优缺点:

    • 优点:

全局控制能力:集群限流可以在整个集群或多个节点上进行流量控制,实现全局的流量控制策略.
高可伸缩性:通过增加节点来扩展处理能力,可以更好地应对高流量的情况.

  • 缺点:

复杂性增加:集群限流需要在多个节点之间进行数据共享和协调,增加了系统的复杂性.
网络开销:集群限流需要节点间的通信和协调,可能会带来一定的网络延迟和额外的开销.

总的来说单机限流适用于对单个节点或服务器进行流量控制的场景,简单、高效,但扩展性有限;
集群限流适用于对整个系统或多个节点进行流量控制的场景,具有全局控制能力和高可伸缩性,但复杂度和网络开销较高.
选择合适的限流方式应根据具体的应用需求和系统架构来决定.

2.2.2 前置限流&后置限流

前置限流和后置限流是两种常见的流量控制方式,它们在应用时机和实现方式上有一些区别,并且各自具有不同的优缺点.

区别:
前置限流:在请求到达服务之前进行流量控制,即在请求进入系统之前进行限流处理.
后置限流:在请求离开服务之后进行流量控制,即在请求处理完成后进行限流处理.

前置限流的优缺点:

  • 优点:

提前过滤请求:前置限流可以在请求进入系统之前进行过滤和拒绝,减轻服务的负担.
快速响应:由于限流操作在请求进入系统之前进行,可以快速地进行限流判断,避免不必要的请求处理.

  • 缺点:

精确度有限:前置限流通常只能基于部分请求信息进行限流判断,无法对具体请求的处理结果进行评估.
需要额外资源:前置限流需要在请求进入系统之前进行处理,可能需要额外的流量控制组件或网络设备.

后置限流的优缺点:

  • 优点:

基于实际处理结果:后置限流可以根据请求的实际处理结果进行限流判断,更加准确地控制流量.
动态调整:后置限流可以根据实时的系统负载和性能情况,动态调整限流策略.

  • 缺点:

延迟较高:后置限流需要等待请求处理完成后才能进行判断和限流操作,可能会引入较高的延迟.
需要额外资源:后置限流需要在请求处理完成后进行处理,可能需要额外的流量控制组件或网络设备.

从上面的描述知道前置限流适用于在请求进入系统之前进行快速的流量控制,能够提前过滤请求和快速响应,但精确度有限;
后置限流适用于根据请求的实际处理结果进行流量控制,能够基于实际情况进行动态调整,但可能引入较高的延迟.

2.3 实际落地是怎么做的

2.3.1 流量链路

在介绍实际的限流方案需要简单介绍下整体流量链路.

在这里插入图片描述

以上是大致的一个流量链路.

step1:从整个入口到网关是整体流量的入口.
step2:从入口到各个 servicenginx
step3:从 nginx 再到本地的 service 接口

目前系统提供的服务分为两个部分.http 接口以及 rpc 接口.先说 http 接口这一块.这块主要支撑的是整个
主站的服务.具体的协议也是 http 的形式.通过反向代理挂载每一个逻辑分组(一个分组多个机器).

2.3.2 各链路限流

2.3.2.1 网关层

首先是整个网关这一层
这一层主要是用来代理整个公司级别的服务.这一层目前是用后置限流来处理进行处理的.

后置限流:后置限流是指在请求处理完之后,再根据请求的处理结果来判断是否需要限制请求的处理速率.即先处理请求,然后根据处理结果来决定是否限流.
这种方式可以确保请求的处理逻辑不受限流策略的影响,但可能会导致一些请求在处理完后才被限制,从而可能导致系统的负载过大.

前置限流:前置限流是指在请求处理之前,根据系统的负载情况或者其他指标来判断是否需要限制请求的处理速率.即在处理请求之前就根据限流策略来决定是否拒绝该请求.
这种方式可以避免系统负载过大,但可能会导致一些请求被提前拒绝,从而可能影响系统的正常运行.

通过上面的描述能知道后置限流的逻辑是其实目的是不干扰整个业务逻辑的执行,但是在整体流量的防控上做一次兜底.其实本质是没有做到下游的系统保护,这种方式的限流对象
仅仅是流量,就没有系统保护这一层的能力.

2.3.2.2 nginx 层

第二层是nginx 到服务这一层.

这一层目前是有两个维度.
根据 request 请求的方法指定方法请求级别的限流.
这个基本是对于单机限流的一个兜底,在服务的集群限流可能出现的一些漂移现象后的系统保护.
还有一个维度是每次请求都通过 lua 获取本地的物理机 cpu 指标. 具体的限制指标度量,可以依照压测对于 cpu 的观测.

-- 执行系统命令并获取输出结果
function execute_command(command)
    local handle = io.popen(command)
    local result = handle:read("*a")
    handle:close()
    return result
end

-- 获取系统的CPU信息
function get_cpu_info()
    local cpu_info = execute_command("cat /proc/cpuinfo")
    return cpu_info
end

-- 示例代码
local cpu_info = get_cpu_info()
print(cpu_info)

2.3.3.3 服务本身的限流

上面的限流基本是在服务本身之外的.或网关或流量代理实现的.
对于大型的分布式系统一般是选用集群限流.能很好的因为一些节点的故障带来的整体流量的承载能力的下载如果是单机限流,在节点故障后相当于整体的系统负载能力就削弱了.

具体的限流阈值有两个点.

  • 或者根据相应的往年的监控流量值来进行推算.
  • 具体根据压测时的阈值来进行调整.

第一点需要系统具备完整的监控体系.以获取足够的历史数据来进行推演.有的同学说了,我的系统或者我的接口是第一次上线无法获取足够的历史监控来推测.
系统第一次上线的场景:只能与 qa 测试这边打一个配合,完整的链路压测来彻底了解你的系统.获取到系统在可接受的接口性能下如:200ms.下的最大 单机qps或整体的集群最大 qps.
如果是单机压测,那么集群的限流就是线上机器数*单机承压的 qps 了.

还有一个点对于集群限流的场景,还是觉得有必要 mark 下.对于 rpc 服务来说,特别是上下游关系复杂调用方较多.这时候有单纯的一个集群限流
往往解决不了问题.很可能需要根据不同的调用方来实现不同的集群限流.例如:对于核心的,量大的 调用方单独的集群限流.如 2 个这种调用方就需要两个
集群限流 key.其他的一些小流量这样可以归并到 other 这样的集群限流渠道就好了.

集群限流的实现方式 sentinel

预取率:指的是在集群限流场景下.本地的调用端对于集群 token 的预取占比.举个例子:集群限流是 10000.预取:7000.那么预取率则是 70%.
假如有 70 台机器.则每个机器的 token 预取个数则是 10.

目前集群限流的过程

在这里插入图片描述

通过图示可以看出图上为令牌桶限流算法.
通过平台设置指定 key 的限流阈值.
consumer 启动完成 token 预取.预取的逻辑很常见.(如:美团的 leaf 的框架也有设计.好的框架总是借鉴来借钱去,这里也是套用电影里的说法
致敬)
实际请求过程中通过token 验证通过,完成对 provider 的调用.验证不通过则触发限流.

赠人玫瑰 手有余香 我是柏修 一名持续更新的晚熟程序员
期待您的点赞,关注加收藏,加个关注不迷路,感谢
您的鼓励是我更新的最大动力
↓↓↓↓↓↓

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

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

相关文章

【Java面试】七、SpringMvc的执行流程、SpringBoot自动装配原理

文章目录 1、SpringMVC的执行流程1.1 视图阶段1.2 前后端分离阶段 2、SpringBoot自动配置原理3、框架常用的注解3.1 Spring的注解3.2 SpringMvc的注解3.3 SpringBoot的注解 4、面试 1、SpringMVC的执行流程 1.1 视图阶段 旧项目中&#xff0c;未前后端分离时&#xff0c;用到…

计算机视觉与模式识别实验2-2 SIFT特征提取与匹配

文章目录 &#x1f9e1;&#x1f9e1;实验流程&#x1f9e1;&#x1f9e1;SIFT算法原理总结&#xff1a;实现SIFT特征检测和匹配通过RANSAC 实现图片拼接更换其他图片再次测试效果&#xff08;依次进行SIFT特征提取、RANSAC 拼接&#xff09; &#x1f9e1;&#x1f9e1;全部代…

(CVPRW,2024)可学习的提示:遥感领域小样本语义分割

文章目录 相关资料摘要引言方法训练基础类别新类别推理 相关资料 论文&#xff1a;Learnable Prompt for Few-Shot Semantic Segmentation in Remote Sensing Domain 代码&#xff1a;https://github.com/SteveImmanuel/OEM-Few-Shot-Learnable-Prompt 摘要 小样本分割是一项…

JMeter性能测试实现与分析分享

JMeter是由著名开源软件巨头Apache组织开发的纯Java的压力测试工具&#xff0c;它即能测试动态服务&#xff08;WebService&#xff09;&#xff0c;也能测试静态资源&#xff0c;包括Servlet服务、CGI脚本等&#xff0c;还能测试动态语言服务&#xff08;PHP、Java、ASP.NET等…

外汇天眼:总是权衡利弊,投资注定失败

投资股票的人往往会频繁地评估自己的投资结果&#xff0c;尤其是在信息时代&#xff0c;手机上随时可以查看股票行情&#xff0c;导致很多人时不时地打开行情软件&#xff0c;看一看自己的股票是涨了还是跌了&#xff0c;盈利了还是亏损了。 频繁评估结果的弊端 一、引发急躁…

JVM学习-监控工具(二)

jmap&#xff1a;导出内存映像文件&内存使用情况 基本情况 jmap(JVM Memory Map)&#xff1a;一方法获取dump文件(堆转储快照文件&#xff0c;二进制文件)&#xff0c;还可以获取目标Java进程的内存相关信息&#xff0c;包括Java堆各区域的使用情况、堆中对象的统计信息、…

Golang TCP网络编程

文章目录 网络编程介绍TCP网络编程服务器监听客户端连接服务器服务端获取连接向连接中写入数据从连接中读取数据关闭连接/监听器 简易的TCP回声服务器效果展示服务端处理逻辑客户端处理逻辑 网络编程介绍 网络编程介绍 网络编程是指通过计算机网络实现程序间通信的一种编程技术…

成功解决“ModuleNotFoundError: No Module Named ‘utils’”错误的全面指南

成功解决“ModuleNotFoundError: No Module Named ‘utils’”错误的全面指南 在Python编程中&#xff0c;遇到ModuleNotFoundError: No Module Named utils这样的错误通常意味着Python解释器无法找到名为utils的模块。这可能是由于多种原因造成的&#xff0c;比如模块确实不存…

推荐一款开源Scada,数据采集必备

软件介绍 PySCADA的核心是一个基于HTML5的HMI&#xff0c;不仅确保现代性&#xff0c;还确保与各种设备的无缝集成。该框架通过提供对广泛工业协议的支持&#xff0c;进一步巩固其功能。这些包括Modbus TCP/IP、RTU、ASCII和Binary&#xff0c;让用户可以轻松地与不同的设备和系…

党历史纪念馆3d沉浸式虚拟展厅为参观者提供了一个身临其境的革命教育世界

传统的展览方式虽然能够传达一定的信息&#xff0c;但往往难以激发参观者的深度参与和共鸣。而廉政教育VR数字化展厅通过VR虚拟现实技术的运用&#xff0c;打破了传统展览的局限性&#xff0c;为参观者提供了一个身临其境的廉政世界。 在这个廉政教育VR数字化展厅中&#xff0c…

高中数学:解三角形-大题练习

例题1 解析 第一小问 根据条件等式&#xff0c;我们发现&#xff0c;每一项都含有边&#xff0c;但是&#xff0c;不是每一项都含有角 于是&#xff0c;我们要想到用正弦定理把边换为角来解答该题 第二小问 例题2 解析 第一小问 两个等式条件&#xff0c;各个项都含有边&…

代码随想录算法训练营第十三天|239. 滑动窗口最大值、 347.前 K 个高频元素

239. 滑动窗口最大值 题目链接&#xff1a;滑动窗口最大值 文档讲解&#xff1a;代码随想录 状态&#xff1a;忘了 思路1&#xff1a;使用优先队列来维护滑动窗口中的最大值&#xff0c;确保在每次滑动时能够高效地找到最大值。时间复杂度是 O(n log k)。 题解&#xff1a; cl…

MyBatis的各种查询功能

1、查询&#xff1a; 查询的标签select必须设置属性resultType或resultMap&#xff0c;用于设置实体类和数据库表的映射关系 resultType&#xff1a;自动映射&#xff0c;用于属性名和表中字段名一致的情况 resultMap&#xff1a;自定义映射&#xff0c;用于一对多或多对一或…

每日一练——分糖果2

1103. 分糖果 II - 力扣&#xff08;LeetCode&#xff09; /*** Note: The returned array must be malloced, assume caller calls free().*/ int* distributeCandies(int candies, int num_people, int* returnSize) {int num 0;int* arr (int*)malloc(sizeof(int)*num_peo…

C语言 链表经典OJ题

链表经典OJ题 移除链表元素链表的中间节点反转链表合并两个有序链表分割链表 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head […

贝锐蒲公英异地组网:降低建筑工地远程视频监控成本、简化运维

中联建设集团股份有限公司是一家建筑行业的施工单位&#xff0c;专注于建筑施工&#xff0c;业务涉及市政公用工程施工总承包、水利水电工程施工总承包、公路工程施工总承包、城市园林绿化专业承包等&#xff0c;在全国各地开展有多个建筑项目&#xff0c;并且项目时间周期可能…

多线程新手村5--线程池

1.1 线程池是什么 线程诞生的意义是因为进程的创建/销毁开销太大&#xff0c;所以使用线程提高代码的执行效率&#xff1b;那如果想要进一步提升执行效率&#xff0c;该怎么办呢&#xff1f;有一个方法是使用线程池。 首先&#xff0c;什么是线程池&#xff1a;池就是池子&am…

使用Python绘制南丁格尔图(玫瑰图)

使用Python绘制南丁格尔图&#xff08;玫瑰图&#xff09; 南丁格尔图效果代码 南丁格尔图 南丁格尔图&#xff08;Nightingale Rose Chart&#xff09;&#xff0c;也被称为玫瑰图或极区图&#xff0c;是一种特殊的圆形统计图&#xff0c;用于显示多个类别的数据。它是由弗洛…

鸿蒙开发接口资源调度:【@ohos.workScheduler (延迟任务调度)】

延迟任务调度 本模块提供延迟任务注册、取消、查询的能力。 开发者在开发应用时&#xff0c;通过调用延迟任务注册接口&#xff0c;注册对实时性要求不高的延迟任务&#xff0c;该任务默认由系统安排&#xff0c;在系统空闲时根据性能、功耗、热等情况进行调度执行。 说明&am…

Linux配置java,maven,marshalsec环境

文章目录 一. Linux配置java环境1.下载jdk文件2.解压tar.gz文件3.设置java环境变量4.验证是否成功 二. Linux配置maven环境1.下载压缩包2.解压tar.gz3. 配置环境变量 三. Linux配置marshalsec环境 一. Linux配置java环境 1.下载jdk文件 mkdir /opt/javawget https://repo.hua…