如何使用滑动窗口限流优化网站性能 —— 安企CMS中的实践

如何优雅地处理高频访问?

今天早上,我收到了一条客户反馈,说网站打开很卡。我立刻打开服务器进行监控,发现服务器的负载异常高。经过一番排查,我发现在极短的时间内,某个IP以非常规律的频率访问着网站的多个页面。几乎一眼就能看出,网站被恶意的采集工具盯上了。这个IP通过不断请求页面,极大地消耗了服务器资源,导致正常用户无法访问。

面对此类高频请求问题,如果不采取有效的限流措施,网站不仅会出现性能问题,还可能在遭受持续攻击的情况下直接崩溃。于是,我决定着手处理这个问题,设计一套高效的请求限流方案。

传统限流方式面临的挑战

很多现有的限流方案都基于固定时间窗口逐次记录请求时间戳。这些方法虽然能解决部分问题,但在实际项目中有几个明显的缺点:

  1. 固定时间窗口:它按固定时段(如1分钟、5分钟)计数,当时间窗口切换时,所有请求记录清零。这样很容易导致“突发请求”问题:刚刚进入新窗口时,计数器归零,瞬间允许大量请求通过。

  2. 时间戳记录:逐次记录每次请求的时间戳虽然精确,但它要求对每个IP记录大量请求数据,内存占用较大,特别是在高流量网站上,容易出现性能瓶颈。

显然,这些传统方法难以应对我的需求。于是,我开始寻找一种更高效且灵活的方案。

如何选择最优解?

在进行方案对比时,我考虑了以下几种解决方案:

  • 漏桶算法(Leaky Bucket):将请求当成水滴,滴入“桶”中,按照固定速率“漏”出。这个方法虽然能平滑处理请求流量,但对于高频突发的请求依然存在难以控制的情况。

  • 令牌桶算法(Token Bucket):类似于漏桶算法,但它允许在短时间内处理请求的“突发”,只要有足够的“令牌”。然而,令牌桶算法相对复杂,且需要不断生成令牌,管理难度较大。

  • 滑动窗口计数法(Sliding Window):通过动态滑动的时间窗口统计请求,不仅能够灵活应对突发流量,还能保证整个窗口期内的请求量精确计算,避免传统固定时间窗口的缺点。

经过对比,我最终选择了滑动窗口计数法。这种方法既能有效限制请求频率,又不会像记录时间戳那样占用大量内存。

滑动窗口 + 时间桶

为了优化滑动窗口的内存使用,我设计了一个基于时间桶的滑动窗口算法。该方案不需要逐次记录每个请求的时间戳,而是将整个窗口期分成多个“时间桶”,每个桶记录1分钟内的请求总数。通过动态滑动这些桶,我们可以精准控制5分钟内的请求总量。

核心思路:

  1. 滑动窗口:将时间窗口分成5个1分钟的桶,每当新的一分钟开始时,移除最早的1分钟数据,动态计算最新的5分钟请求总量。
  2. 时间桶:每个桶存储该分钟内的请求数量,而不是每个请求的时间戳。这极大降低了内存占用。
  3. IP白名单:同时,我还引入了IP白名单,内网和本地IP无需受到限流控制,确保正常流量不受影响。
  4. UA白名单:同样,我引入了UA白名单,对特定UA的请求不做限流控制,避免了搜索引擎蜘蛛被误伤。

实现代码:

定义数据结构
type VisitInfo struct {Buckets     [5]int    // 每分钟一个桶,共5个桶LastVisit   int64     // 上次请求的时间戳CurrentIdx  int       // 当前时间对应的桶索引TotalCount  int       // 当前窗口期内的请求总数
}var ipVisits = make(map[string]*VisitInfo)
var blockedIPs = make(map[string]time.Time)
var mu sync.Mutexconst WindowSize = 5 * time.Minute  // 窗口大小为5分钟
const MaxRequests = 100             // 5分钟内最大请求次数
const BlockDuration = 1 * time.Hour // 封禁时长为1小时var whiteListIPs = []string{"127.0.0.1", "192.168.0.0/16"} // 内网和本地IP白名单func isWhitelisted(ip string) bool {for _, cidr := range whiteListIPs {_, subnet, _ := net.ParseCIDR(cidr)if subnet.Contains(net.ParseIP(ip)) {return true}}return false
}
记录请求并清理过期记录
func recordIPVisit(ip string) bool {mu.Lock()defer mu.Unlock()now := time.Now().Unix() // 当前的Unix时间(秒)currentMinute := now / 60 % 5 // 当前在5个桶中的索引// 检查是否已有该IP的访问记录visitInfo, exists := ipVisits[ip]if !exists {visitInfo = &VisitInfo{Buckets:    [5]int{},LastVisit:  now,CurrentIdx: int(currentMinute),}ipVisits[ip] = visitInfo}// 计算时间差,更新桶的状态elapsedMinutes := int(now/60 - visitInfo.LastVisit/60)// 如果时间超过了窗口大小,重置所有桶if elapsedMinutes >= 5 {visitInfo.Buckets = [5]int{}visitInfo.TotalCount = 0} else {// 依次清理过期的桶for i := 1; i <= elapsedMinutes; i++ {idx := (visitInfo.CurrentIdx + i) % 5visitInfo.TotalCount -= visitInfo.Buckets[idx]visitInfo.Buckets[idx] = 0}}// 更新当前桶的索引和计数visitInfo.CurrentIdx = int(currentMinute)visitInfo.Buckets[visitInfo.CurrentIdx]++visitInfo.TotalCount++// 更新最后访问时间visitInfo.LastVisit = now// 检查是否超过最大请求次数if visitInfo.TotalCount > MaxRequests {return false // 超过最大请求次数,应该封禁}return true
}
处理请求逻辑
func handleRequest(w http.ResponseWriter, r *http.Request) {ip := r.RemoteAddr// 检查并跳过白名单和搜索引擎if isWhitelisted(ip) || !isUAWhitelisted(r.UserAgent()) {...}// 检查IP是否已被封禁if isIPBlocked(ip) {http.Error(w, "Your IP is blocked.", http.StatusForbidden)return}// 记录IP访问,并检查是否超出阈值if !recordIPVisit(ip) {blockIP(ip)http.Error(w, "Too many requests from this IP.", http.StatusTooManyRequests)return}// 正常处理请求...
}
定时清理封禁的IP
func cleanupExpiredRecords() {mu.Lock()defer mu.Unlock()now := time.Now()// 清理过期的封禁记录for ip, unblockTime := range blockedIPs {if now.After(unblockTime) {delete(blockedIPs, ip)}}// 清理过期的IP访问记录,这里只回收最后一次访问超过5分钟的记录for ip, visitInfo := range ipVisits {if now.After(time.Unix(visitInfo.LastVisit, 0).Add(WindowSize)) {delete(ipVisits, ip)}}
}func startCleanupTask() {ticker := time.NewTicker(1 * time.Minute)go func() {for range ticker.C {cleanupExpiredRecords()}}()
}

当请求到来时,系统首先检查该IP是否在白名单中。如果是白名单IP,直接放行;如果不是,则使用滑动窗口算法动态统计请求数量。

判断UA,如果是搜索引擎蜘蛛,则也同样跳过后续的检查,直接放行。

将滑动窗口集成到安企CMS中

将滑动窗口限流方案集成到安企CMS时,我主要关注以下几点:

  1. 高效性:确保限流逻辑在高并发情况下依然能够快速处理,不影响正常请求。
  2. 灵活性:通过调节时间桶数量和每个桶的大小,适应不同的流量场景。例如,系统默认5分钟内允许100次请求,但可以根据业务需求灵活调整。
  3. 稳定性:对封禁的IP进行1小时的封禁处理,并定期清理过期的封禁记录,确保系统长时间稳定运行。

总结:滑动窗口限流在安企CMS中的应用

通过滑动窗口和时间桶相结合的方法,我成功解决了安企CMS中的恶意请求问题。该方案不仅显著降低了内存开销,还使得系统在高流量下表现稳定。特别是在集成了IP白名单功能后,内网和本地IP用户可以免受限流影响,保证了系统对内部流量的友好性。

优点:

  • 高效:相比逐次记录请求时间戳的传统方法,内存占用和计算量大幅减少。
  • 灵活:可以根据业务需求灵活调整限流策略和封禁时长。
  • 安全:封禁机制有效防止恶意用户对系统发起过多请求,提升整体安全性。

这次滑动窗口限流方案的实践,不仅提升了安企CMS的性能和稳定性,也为其他开发者提供了一个简单易用的高效限流方案。如果你也在开发过程中遇到类似的高频请求问题,希望这篇文章能为你提供一些参考。

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

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

相关文章

2024年华为杯中国研究生数学建模竞赛F题保姆级教程思路分析

2024年中国研究生数学建模竞赛F题保姆级教程思路分析 F题题目&#xff1a;X射线脉冲星光子到达时间建模 本题目围绕脉冲星导航与X射线光子到达时间建模展开。脉冲星由于其自转稳定性和规律性&#xff0c;被认为是宇宙中精确的时钟&#xff0c;并可以用作航天器的定位和导航基…

Ubuntu 22.04 源码下载、编译

Kernel/BuildYourOwnKernel - Ubuntu Wikihttps://wiki.ubuntu.com/Kernel/BuildYourOwnKernel 一、查询当前系统内核版本 rootubuntu22:~# uname -r 5.15.0-118-generic 二、查询本地软件包数据库中的内核源码信息 rootubuntu22:~# apt search linux-source Sorting... Do…

使用Maven创建一个Java项目并在repository中使用

JDK环境&#xff1a;1.8.0_371 Maven环境 &#xff1a;Apache Maven 3.6.3 配置完成jdk和mvn后&#xff0c;进入到指定文件夹下执行如下语句&#xff1a; mvn archetype:generate -DgroupIdtop.chengrongyu -DartifactIdCyberSpace -DarchetypeArtifactIdmaven-archetype-quic…

(20)Shell脚本的书写

linux中的shell脚本&#xff0c;其实是系统中真正的命令。Shell语言写的程序不需编译.定义各种参数和变量、使用条件命令、控制结构以及其他高级特性。 一、shell脚本基本元素 1.1变量 定义&#xff1a;定义一个名称&#xff0c;将参数赋予给这个名称。就叫变量。变量名可以…

C++【类和对象】(一)

文章目录 前言1.类的定义1.1类定义格式1.2 访问限定符1.3 类域 2. 实例化2.1 实例化的概念2.2 对象大小 3.this指针结语 前言 在前文我们讲解了C基础语法知识。本文将会讲解C的类和对象。 1.类的定义 1.1类定义格式 class name {}&#xff1b;class为定义类的关键字&#x…

Linux进阶命令-rsync

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 经过上一章Linux日志的讲解&#xff0c;我们对Linux系统自带的日志服务已经有了一些了解。我们接下来将讲解一些进阶命令&am…

erlang学习:Linux常用命令2

目录操作命令 对目录进行基本操作 相关cd切换目录之类的就直接省去了&#xff0c;以下操作中都会用到 查看当前目录下的所有目录和文件 ls 列表查看当前目录下的所有目录和文件&#xff08;列表查看&#xff0c;显示更多信息&#xff09; ls -l 或 ll 在当前目录下创建一个…

高性能并发计数器的比较

参考文档&#xff1a;https://baijiahao.baidu.com/s?id1742540809477784106&wfrspider&forpc 一、常用的并发计数方法 1、synchronized synchronized早期是一个重量级锁&#xff0c;因为线程竞争锁会引起操作系统用户态和内核态切换&#xff0c;浪费资源&#xff…

Element Plus中button按钮相关大全

一、基本用法 使用 type、plain、round 和 circle 来定义按钮的样式。 样式代码如下&#xff1a; <template><div class"mb-4"><el-button>Default</el-button><el-button type"primary">Primary</el-button><el…

C语言常见字符串函数模拟实现一

strlen模拟实现 重点&#xff1a;1.字符串已经\0作为结束标志&#xff0c;strlen返回的是字符串\0前面出现的字符个数&#xff08;不包含\0&#xff09; 2.参数指向的字符串必须要以\0结束。 3.注意函数的返回值是size_t&#xff0c;是无符号的&#xff0c;加减是无法对比的。…

卡西欧相机SD卡格式化后数据恢复指南

在数字摄影时代&#xff0c;卡西欧相机以其卓越的性能和便携性成为了众多摄影爱好者的首选。然而&#xff0c;随着拍摄量的增加&#xff0c;SD卡中的数据管理变得尤为重要。不幸的是&#xff0c;有时我们可能会因为操作失误或系统故障而将SD卡格式化&#xff0c;导致珍贵的照片…

数据类型转换中存在的问题分析

隐式类型转换&#xff08;implicit type conversion&#xff09; 隐式类型转换&#xff08;implicit type conversion&#xff09;包括整型提升&#xff08;integer promotion&#xff09;和标准算数转换&#xff08;usual arithmetic conversions&#xff09; 遵循较大范围优…

堡垒机(Bastion Host)概述

Bastion Host 堡垒机 一、什么是堡垒机&#xff1f; A bastion host is a computer specially designed to mitigate cyberattacks and manage access rights to an internal network. 堡垒机Bastion Host是一种专门设计用于缓解网络攻击并管理内部网络访问权限的计算机。 在…

肖扬新书《微权力下的项目管理》读书笔记2

一个核心思想&#xff1a;“借力” 合格的项目经理是不热衷于培养人的。项目经理的工作场景和职能经理的工作场景往往有很 大不同。职能经理的工作方式通常适用于常态化工作&#xff0c;要有足够的时间去培养人&#xff0c;先把人培 养起来&#xff0c;然后再干事&#xff0c;可…

加油卡APP定制搭建,让加油更便捷!

在汽车时代中&#xff0c;汽车的数量不断增加&#xff0c;加油已经成为了大众生活中不可缺少的一部分。同时&#xff0c;加油卡的出现也为大众的汽车加油提供了更多的优惠方式&#xff0c;为大众节省经济开支&#xff0c;为车主带来便利&#xff1b;同时加油卡的发展也提高了加…

2024年华为杯研赛(E题)数学建模竞赛解题思路|完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

如何远程访问局域网内的电脑?局域网内远程桌面怎么实现?揭秘4种干货技巧

想象一下&#xff0c;你正在办公室A&#xff0c;而你想访问办公室B里的某台电脑&#xff0c;却不想起身到另一楼层甚至是另一个房间。 如何不动身就能控制局域网内的另一台电脑呢&#xff1f; 这并不是科幻&#xff0c;而是完全可以通过远程桌面技术来实现。 今天&#xff0…

学习Java(一)类和对象

package demo.ceshi;public class Puppy {private int age;private String name;//构造器public Puppy( String name){this.name name;System.out.println("公主的名字叫&#xff1a;"name);}//设置age的值public void setAge(int age){this.age age;System.out.pr…

智慧仓储-AI销量预测

1、预测系统技术选型 基础层&#xff1a; Hbase、ClickHouse、Hdfs 用来做数据存储 框架层&#xff1a; 以 Spark RDD、Spark SQL、Hive 为主&#xff0c; MapReduce 程序占一小部分&#xff0c;是原先遗留下来的&#xff0c;目前正逐步替换成 Spark RDD。 选择 Spark 除了对…

rsyslogd 内存占用很高解决方案

在Kubernetes&#xff08;K8S&#xff09;集群中&#xff0c;监控日志是非常重要的&#xff0c;而rsyslogd是Linux系统中用于处理系统和应用程序日志的守护进程。有时候rsyslogd可能会占用较高的内存&#xff0c;这时候我们就需要对其进行优化和调整。 阿里云虚拟服务器&…