软件系统限流的底层原理解析

作者:腾讯云天御业务安全工程师knightwwang

在软件架构中,限流是一种控制资源使用和保护系统安全的重要机制。它通过限制在一定时间内可以处理的请求数量,来防止系统过载。

1.限流的目的

限流主要有两个目的:

2.限流算法的实现

2.1固定窗口计数器算法

固定窗口计数器算法是一种基本的限流方法,它通过在固定时间窗口内跟踪请求的数量来实现限流。

//这是一个简单的实现案例

packagemain

import(

"fmt"

"sync"

"time"

)

//FixedWindowCounter结构体实现固定窗口计数器限流算法。

//mu用于同步访问,保证并发安全。

//count记录当前时间窗口内的请求数量。

//limit是时间窗口内允许的最大请求数量。

//window记录当前时间窗口的开始时间。

//duration是时间窗口的持续时间。

typeFixedWindowCounterstruct{

musync.Mutex

countint

limitint

windowtime.Time

durationtime.Duration

}

//NewFixedWindowCounter构造函数初始化FixedWindowCounter实例。

//limit参数定义了每个时间窗口内允许的请求数量。

//duration参数定义了时间窗口的大小。

funcNewFixedWindowCounter(limitint,durationtime.Duration)*FixedWindowCounter{

return&FixedWindowCounter{

limit:limit,

window:time.Now(),//设置当前时间作为窗口的开始时间。

duration:duration,//设置时间窗口的持续时间。

//Allow方法用于判断当前请求是否被允许。

//首先通过互斥锁保证方法的原子性。

func(f*FixedWindowCounter)Allow()bool{

f.mu.Lock()

deferf.mu.Unlock()

now:=time.Now()//获取当前时间。

//如果当前时间超过了窗口的结束时间,重置计数器和窗口开始时间。

ifnow.After(f.window.Add(f.duration)){

f.count=0

f.window=now

//如果当前计数小于限制,则增加计数并允许请求。

iff.counts.windowDuration{

s.slideWindow()

now:=time.Now()

index:=int((now.UnixNano()-s.windowStart.UnixNano())/s.interval.Nanoseconds())%len(s.counters)

ifs.counters[index]t.capacity{

t.tokens=t.capacity//确保令牌数不超过桶的容量。

//如果桶中有令牌,则移除一个令牌并允许请求通过。

ift.tokens>0{

t.tokens--//移除一个令牌。

t.lastRefill=now//更新上次填充时间到当前时间。

returntrue

//如果桶中无令牌,则请求被拒绝。

returnfalse

//main函数是程序的入口点。

funcmain(){

//创建一个新的令牌桶实例,桶的容量为10,每秒填充2个令牌。

limiter:=NewTokenBucket(10,2)

//模拟请求,观察限流效果。

//循环15次,每次请求判断是否被允许。

fori:=0;i=r.limit{

//允许请求逻辑...

请求分类

请求分类允许对不同类型的请求应用不同的限流规则,例如,对API的不同端点设置不同的阈值。

伪代码示例

//RouteLimiterMap是一个映射,存储每个路由路径对应的限流器实例。

//键是路由的字符串表示,值是指向RateLimiterV2类型实例的指针。

varRouteLimiterMap=map[string]*RateLimiterV2{}

//SetRateLimiterForRoute函数为指定的路由设置一个新的限流器。

//它接受路由的路径、桶的容量、每秒填充的令牌数和请求处理的阈值作为参数,

//并创建一个新的RateLimiterV2实例,将其存储在RouteLimiterMap中。

funcSetRateLimiterForRoute(routestring,capacityint,refillRatefloat64,limitint){

//在RouteLimiterMap中为给定的路由创建或更新限流器实例。

RouteLimiterMap[route]=NewRateLimiterV2(capacity,refillRate,limit)

//MiddlewareWithRoute函数返回一个Gin中间件处理函数。

//该中间件基于路由名称来应用限流逻辑。

funcMiddlewareWithRoute(routestring)gin.HandlerFunc{

//返回一个Gin的处理函数,该函数内部封装了限流逻辑。

returnfunc(c*gin.Context){

//检查RouteLimiterMap中是否存在对应路由的限流器。

//如果存在,调用其Allow方法来决定当前请求是否应该被允许。

if!RouteLimiterMap[route].Allow(){

//如果请求被限流(不允许),返回HTTP429状态码和错误信息。

c.JSON(http.StatusTooManyRequests,gin.H{"error":"toomanyrequests"})

c.Abort()//中断请求的进一步处理。

return//退出中间件函数。

//如果请求未被限流,调用c.Next继续执行Gin的处理链。

c.Next()

反馈机制

反馈机制在请求被限流时向用户提供适当的反馈,如错误消息或重试后的时间。

//AllowWithFeedback提供反馈的请求允许逻辑。

func(r*RateLimiterV2)AllowWithFeedback()(bool,string){

r.mu.Lock()

deferr.mu.Unlock()

//令牌桶逻辑...

ifr.tokens>=r.limit{

returnfalse,"Toomanyrequests.Pleasetryagainlater."

r.tokens--//移除令牌

returntrue,""

//使用反馈机制的中间件。

funcMiddlewareWithFeedback()gin.HandlerFunc{

allowed,message:=RouteLimiterMap["/api/"].AllowWithFeedback()

if!allowed{

c.JSON(http.StatusTooManyRequests,gin.H{"error":message})

c.Abort()

return

5.限流的考虑因素

在设计和实施限流机制时,需要综合考虑多个关键因素以确保限流系统的有效性和公平性。

公平性

公平性是限流设计中的首要原则,确保所有用户和客户端能够平等地访问服务。

//FairLimiter结构体实现基于用户ID或IP的公平限流。

typeFairLimiterstruct{

sync.Mutex

limitsmap[string]*RateLimiterV2//为每个用户或IP维护一个独立的限流器

//NewFairLimiter创建一个新的FairLimiter实例。

funcNewFairLimiter(capacityint,refillRatefloat64)*FairLimiter{

return&FairLimiter{

limits:make(map[string]*RateLimiterV2),

//Allow根据用户ID或IP决定是否允许请求。

func(f*FairLimiter)Allow(userIDstring)(bool,string){

f.Lock()

deferf.Unlock()

if_,exists:=f.limits[userID];!exists{

//如果用户没有限流器,则创建一个新的。

f.limits[userID]=NewRateLimiterV2(capacity,refillRate,limit)

//使用用户的限流器检查请求。

returnf.limits[userID].AllowWithFeedback()

灵活性

灵活性意味着限流策略能够适应不同的流量模式和业务需求,例如在高流量期间放宽限制。

//FlexibleLimiter结构体是一个灵活的限流器,允许在运行时动态调整限流参数。

typeFlexibleLimiterstruct{

sync.Mutex//使用sync.Mutex提供互斥锁功能,确保线程安全。

capacityint//桶的容量,表示最多可以存储的令牌数。

refillRatefloat64//令牌的填充速率,表示每秒可以新增的令牌数。

limitint//请求处理的阈值,用于确定是否限流。

//SetParams方法允许动态设置FlexibleLimiter的限流参数。

//这些参数包括桶的容量、填充速率和请求处理的阈值。

func(f*FlexibleLimiter)SetParams(capacityint,refillRatefloat64,limitint){

f.Lock()//使用互斥锁进入临界区,防止并发访问导致的数据不一致。

deferf.Unlock()//离开临界区前自动释放锁。

//更新FlexibleLimiter的参数。

f.capacity,f.refillRate,f.limit=capacity,refillRate,limit

//Allow方法根据FlexibleLimiter当前的参数决定是否允许新的请求。

//它首先基于当前参数创建一个新的RateLimiterV2实例,然后调用它的AllowWithFeedback方法。

func(f*FlexibleLimiter)Allow()(bool,string){

//根据FlexibleLimiter当前的容量、填充速率和阈值创建一个新的RateLimiterV2实例。

rl:=NewRateLimiterV2(f.capacity,f.refillRate,f.limit)

//调用RateLimiterV2的AllowWithFeedback方法,获取是否允许请求的反馈。

//这个方法返回一个布尔值表示是否允许请求,和一个字符串消息提供反馈信息。

returnrl.AllowWithFeedback()

透明性

透明性要求限流规则和当前状态对用户可见,使用户能够了解他们被限流的原因和情况。

//TransparentLimiter结构体嵌入了RateLimiterV2,提供了额外的状态信息,

//包括当前剩余的令牌数,以增强限流机制的透明性。

typeTransparentLimiterstruct{

*RateLimiterV2//嵌入RateLimiterV2,获得其所有功能。

currentTokensint//存储当前桶中剩余的令牌数。

//AllowWithStatus方法允许请求并返回当前限流状态。

//它调用内嵌RateLimiterV2的AllowWithFeedback方法来决定是否允许请求,

//并获取反馈消息,同时返回当前剩余的令牌数。

func(t*TransparentLimiter)AllowWithStatus()(bool,string,int){

allowed,message:=t.RateLimiterV2.AllowWithFeedback()//调用内嵌限流器的允许逻辑。

returnallowed,message,t.currentTokens//返回是否允许、消息和当前令牌数。

//MiddlewareWithTransparency函数创建一个中间件,用于在HTTP响应中包含限流状态。

//这个中间件包装了下一个http.Handler,并在处理请求之前检查限流状态。

funcMiddlewareWithTransparency(nexthttp.Handler)http.Handler{

returnhttp.HandlerFunc(func(whttp.ResponseWriter,r*http.Request){

//创建或使用全局的transparentLimiter实例来检查限流状态。

allowed,message,tokens:=transparentLimiter.AllowWithStatus()

//如果请求被限流(不允许),软件系统限流的底层原理解析则设置HTTP头部信息和状态码,并返回错误消息。

w.Header().Set("X-RateLimit-Remaining",fmt.Sprintf("%d",tokens))//设置剩余令牌数的头部。

w.WriteHeader(http.StatusTooManyRequests)//设置HTTP状态码为429。

fmt.Fprintln(w,message)//写入错误消息到响应体。

return//中断请求处理。

//如果请求未被限流,继续执行后续的处理链。

next.ServeHTTP(w,r)

})

免责声明:本网站部分内容由用户自行上传,若侵犯了您的权益,请联系我们处理,谢谢!联系QQ:2760375052

分享:

扫一扫在手机阅读、分享本文