首页 » 网站建设 » phpredistimewait技巧_分布式限流筹划的探索与实践

phpredistimewait技巧_分布式限流筹划的探索与实践

访客 2024-12-13 0

扫一扫用手机浏览

文章目录 [+]

随着微做事的盛行,做事之间的依赖性和调用关系变得越来越繁芜,做事的稳定性变得尤为主要。
业务场景中常常会涉及到瞬时流量冲击,可能会导致要求相应超时,乃至做事器被压垮、宕机不可用。
出于对系统本身和高下游做事的保护,我们常日会对要求进行限流处理,快速谢绝超出配置上限的要求,担保系统或高下游做事系统的稳定。
合理策略能有效应对流量冲击,确保系统可用性和性能。
本文详细先容了几种限流算法,比较各个算法的优缺陷,给出了限流算法选型的一些建议,同时对业务上常用的分布式限流也提出一些办理方案。

1 背景

保护高并发做事稳定紧张有三把利器:缓存、降级和限流。

phpredistimewait技巧_分布式限流筹划的探索与实践

缓存:缓存是一种提高数据读取性能的技能,通过在内存中存储常常访问的数据,可以减少对数据库或者其他存储系统的访问,从而提高系统的相应速率。
缓存可以运用在多个层次,例如浏览器缓存、CDN 缓存、反向代理缓存、运用缓存等。
降级:降级是在系统压力过大或者部分做事不可用的情形下,暂时关闭一些非核心的做事,以担保核心做事的正常运行。
降级可以在多个层次进行,例如页面降级、功能降级、做事降级等。
限流:限流是一种掌握系统处理要求的速率的技能,以防止系统过载。
限流可以通过多种算法实现,例如令牌桶算法、漏桶算法等。

这三把"利器"各有其特点,常日会结合利用,以达到最佳的效果。
例如,可以通过缓存来减少数据库的访问,通过降级来应对系统故障,通过限流来防止系统过载。
在设计高并发系统时,须要根据系统的详细需求和特点,合理地利用这些技能。
接下来本文会紧张先容一些业界常用的限流方法。

phpredistimewait技巧_分布式限流筹划的探索与实践
(图片来自网络侵删)
2 限流知识概述

限流是一种对要求或并发数进行限定的关键技能手段,旨在保障系统的正常运行。
当做事资源有限、处理能力有限时,限流可以对调用做事的上游要求进行限定,以防止自身做事因资源耗尽而停滞做事。

在限流中,有两个主要的观点须要理解:

阈值:指在单位韶光内许可的要求量。
例如,将 QPS(每秒要求数)限定为500,表示在1秒内最多接管500次要求。
通过设置得当的阈值,可以掌握系统的负载,避免过多的要求导致系统崩溃或性能低落。
谢绝策略:用于处理超过阈值的要求的策略。
常见的谢绝策略包括直接谢绝、排队等待等。
直接谢绝会立即谢绝超过阈值的要求,而排队等待则将要求放入行列步队中,按照一定的规则进行处理。
选择得当的谢绝策略可以平衡系统的稳定性和用户体验。

通过合理设置阈值和选择适当的谢绝策略,限流技能可以帮助系统应对突发的要求量激增、恶意用户访问或要求频率过高的情形,保障系统的稳定性和可用性。
限流方案根据限流范围,可分为 单机限流和分布式限流 ;个中单机限流依据算法,又可分为 固定窗口、滑动窗口、漏桶和令牌桶限流等常见四种 。
本文将对上述限流方案进行详细先容。

3 限流基本算法3.1 固定窗口限流3.1.1 算法先容

固定窗口算法是一种大略直不雅观的限流算法,其事理是将韶光划分为固定大小的窗口,在每个窗口内限定要求的数量或速率。
详细实现时,可以利用一个计数器来记录当前窗口内的要求数,并与预设的阈值进行比较。
固定窗口算法的事理如下:

将韶光划分固定大小窗口,例如每秒一个窗口。
在每个窗口内,记录要求的数量。
当有要求到达时,将要求计数加一。
如果要求计数超过了预设的阈值(比如3个要求),谢绝该要求。
窗口结束后,重置要求计数。
3.1.2 代码实现

type FixedWindowLimiter struct { windowSize time.Duration // 窗口大小 maxRequests int // 最大要求数 requests int // 当前窗口内的要求数 lastReset int64 // 上次窗口重置韶光(秒级韶光戳) resetMutex sync.Mutex // 重置锁}func NewFixedWindowLimiter(windowSize time.Duration, maxRequests int) FixedWindowLimiter { return &FixedWindowLimiter{ windowSize: windowSize, maxRequests: maxRequests, lastReset: time.Now().Unix(), }}func (limiter FixedWindowLimiter) AllowRequest() bool { limiter.resetMutex.Lock() defer limiter.resetMutex.Unlock() // 检讨是否须要重置窗口 if time.Now().Unix()-limiter.lastReset >= int64(limiter.windowSize.Seconds()) { limiter.requests = 0 limiter.lastReset = time.Now().Unix() } // 检讨要求数是否超过阈值 if limiter.requests >= limiter.maxRequests { return false } limiter.requests++ return true}func main() { limiter := NewFixedWindowLimiter(1time.Second, 3) // 每秒最多许可3个要求 for i := 0; i < 15; i++ { now := time.Now().Format("15:04:05") if limiter.AllowRequest() { fmt.Println(now + " 要求通过") } else { fmt.Println(now + " 要求被限流") } time.Sleep(100 time.Millisecond) }}

实行结果:

3.1.3 优缺陷

优点:

实现大略:固定窗口算法的实现相对大略,易于理解和支配。
稳定性较高:对付突发要求能够较好地限定和掌握,稳定性较高。
易于实现速率掌握:固定窗口算法可以很随意马虎地限定要求的速率,例如每秒最多许可多少个要求。

缺陷:

要求分布不屈均:固定窗口算法中,窗口内的要求分布可能不屈均,导致某些窗口内的要求数量超过阈值,而其他窗口内的要求较少。
无法应对突发流量:固定窗口算法的窗口大小是固定的,无法灵巧地应对突发流量。
要求的不公正性:窗口结束时的要求计数重置可能导致要求的不公正性。
例如,在窗口结束前的末了一秒内,要求计数已满,而在窗口开始时的第一秒内,要求计数为零。

固定窗口算法适用于对要求速率有明确哀求且流量相对稳定的场景,但对付突发流量和要求分布不屈均的情形,可能须要考虑其他更灵巧的限流算法。

3.2 滑动窗口限流3.2.1 算法先容

上文已经解释当碰着韶光窗口的临界突变时,固定窗口算法可能无法灵巧地应对流量的变革。
例如,在一个韶光窗口结束时,如果溘然涌现大量要求,固定窗口算法可能会导致要求被谢绝,纵然不才一个韶光窗口内的要求并不多。
这种情形下,我们须要一种更灵巧的算法来应对突发流量和要求分布不屈均的情形—滑动窗口算法。
该算法是对固定窗口算法的一种改进,它通过动态调度窗口的大小来更好地适应流量的变革。
与固定窗口算法不同,滑动窗口算法可以在碰着下一个韶光窗口之前调度窗口的大小,以便更好地掌握要求的速率。
算法的事理如下:

窗口大小:确定一个固定的窗口大小,例如1秒。
要求计数:在窗口内,每次有要求到达时,将要求计数加1。
限定条件:如果窗口内的要求计数超过了设定的阈值,即超过了许可的最大要求数,就谢绝该要求。
窗口滑动:随着韶光的推移,窗口会不断滑动,移除过期的要求计数,以保持窗口内的要求数在限定范围内。
动态调度:在滑动窗口算法中,我们可以根据实际情形调度窗口的大小。
当碰着下一个韶光窗口之前,我们可以根据当前的流量情形来调度窗口的大小,以适应流量的变革。
3.2.2 代码实现

package mainimport ( "fmt" "sync" "time")type SlidingWindowLimiter struct { windowSize time.Duration // 窗口大小 maxRequests int // 最大要求数 requests []time.Time // 窗口内的要求韶光 requestsLock sync.Mutex // 要求锁}func NewSlidingWindowLimiter(windowSize time.Duration, maxRequests int) SlidingWindowLimiter { return &SlidingWindowLimiter{ windowSize: windowSize, maxRequests: maxRequests, requests: make([]time.Time, 0), }}func (limiter SlidingWindowLimiter) AllowRequest() bool { limiter.requestsLock.Lock() defer limiter.requestsLock.Unlock() // 移除过期的要求 currentTime := time.Now() for len(limiter.requests) > 0 && currentTime.Sub(limiter.requests[0]) > limiter.windowSize { limiter.requests = limiter.requests[1:] } // 检讨要求数是否超过阈值 if len(limiter.requests) >= limiter.maxRequests { return false } limiter.requests = append(limiter.requests, currentTime) return true}func main() { limiter := NewSlidingWindowLimiter(500time.Millisecond, 2) // 每秒最多许可4个要求 for i := 0; i < 15; i++ { now := time.Now().Format("15:04:05") if limiter.AllowRequest() { fmt.Println(now + " 要求通过") } else { fmt.Println(now + " 要求被限流") } time.Sleep(100 time.Millisecond) }}

实行结果:

3.2.3 优缺陷

优点:

灵巧性:滑动窗口算法可以根据实际情形动态调度窗口的大小,以适应流量的变革。
这种灵巧性使得算法能够更好地应对突发流量和要求分布不屈均的情形。
实时性:由于滑动窗口算法在每个韶光窗口结束时都会进行窗口滑动,它能够更及时地相应流量的变革,供应更实时的限流效果。
精度:比较于固定窗口算法,滑动窗口算法的颗粒度更小,可以供应更精确的限流掌握。

缺陷:

内存花费:滑动窗口算法须要掩护一个窗口内的要求韶光列表,随着韶光的推移,列表的长度会增长。
这可能会导致较大的内存花费,特殊是在窗口大小较大或要求频率较高的情形下。
算法繁芜性:比较于大略的固定窗口算法,滑动窗口算法的实现较为繁芜。
它须要处理窗口滑动、要求计数和过期要求的移除等逻辑,可能须要更多的代码和打算开销。

从代码的角度可以创造,滑动窗口算法实际上是颗粒度更小的固定窗口算法,它可以在一定程度长进步限流的精度和实时性,并不能从根本上办理要求分布不屈均的问题。
算法受限于窗口的大小和韶光间隔,特殊是在极度情形下,如突发流量过大或要求分布极不屈均的情形下,仍旧可能导致限流不准确。
因此,在实际运用中,要采取更繁芜的算法或策略来进一步优化限流效果。

3.3 漏桶限流3.3.1 算法先容

只管滑动窗口算法可以供应一定的限流效果,但它仍旧受限于窗口的大小和韶光间隔。
在某些情形下,突发流量可能会导致窗口内的要求数超过限定。
为了更好地平滑要求的流量,漏桶限流算法可以作为滑动窗口算法的改进。
算法的事理很大略:它掩护一个固定容量的漏桶,要求以不定的速率流入漏桶,而漏桶以固定的速率流出。
如果要求到达时,漏桶已满,则会触发谢绝策略。

可以从生产者-消费者模式去理解这个算法,要求充当生产者,每个要求就像一滴水,当要求到达时,它被放入一个行列步队(漏桶)中。
而漏桶则充当消费者,以固定的速率从行列步队中消费要求,就像从桶底的孔洞中不断漏出水点。
消费的速率即是限流阈值,例如每秒处理2个要求,即500毫秒消费一个要求。
漏桶的容量就像行列步队的容量,当要求堆积超过指定容量时,会触发谢绝策略,即新到达的要求将被丢弃或延迟处理。
算法的实现如下:

漏桶容量:确定一个固定的漏桶容量,表示漏桶可以存储的最大要求数。
漏桶速率:确定一个固定的漏桶速率,表示漏桶每秒可以处理的要求数。
要求处理:当要求到达时,生产者将要求放入漏桶中。
漏桶流出:漏桶以固定的速率从漏桶中消费要求,并处理这些要求。
如果漏桶中有要求,则处理一个要求;如果漏桶为空,则不处理要求。
要求丢弃或延迟:如果漏桶已满,即漏桶中的要求数达到了容量上限,新到达的要求将被丢弃或延迟处理。
3.3.2 代码实现

package mainimport ( "fmt" "time")type LeakyBucket struct { rate float64 // 漏桶速率,单位要求数/秒 capacity int // 漏桶容量,最多可存储要求数 water int // 当前水量,表示当前漏桶中的要求数 lastLeakMs int64 // 上次漏水的韶光戳,单位秒}func NewLeakyBucket(rate float64, capacity int) LeakyBucket { return &LeakyBucket{ rate: rate, capacity: capacity, water: 0, lastLeakMs: time.Now().Unix(), }}func (lb LeakyBucket) Allow() bool { now := time.Now().Unix() elapsed := now - lb.lastLeakMs // 漏水,根据韶光间隔打算漏掉的水量 leakAmount := int(float64(elapsed) / 1000 lb.rate) if leakAmount > 0 { if leakAmount > lb.water { lb.water = 0 } else { lb.water -= leakAmount } } // 判断当前水量是否超过容量 if lb.water > lb.capacity { lb.water-- // 如果超过容量,减去刚刚增加的水量 return false } // 增加水量 lb.water++ lb.lastLeakMs = now return true}func main() { // 创建一个漏桶,速率为每秒处理3个要求,容量为4个要求 leakyBucket := NewLeakyBucket(3, 4) // 仿照要求 for i := 1; i <= 15; i++ { now := time.Now().Format("15:04:05") if leakyBucket.Allow() { fmt.Printf(now+" 第 %d 个要求通过\n", i) } else { fmt.Printf(now+" 第 %d 个要求被限流\n", i) } time.Sleep(200 time.Millisecond) // 仿照要求间隔 }}

实行结果:

3.3.3 优缺陷

优点:

平滑流量:漏桶算法可以平滑突发流量,使得输出流量变得更加平稳,避免了流量的溘然增大对系统的冲击。
大略易实现:漏桶算法的事理大略,实现起来也相对随意马虎。
有效防止过载:通过掌握流出的流量,漏桶算法可以有效地防止系统过载。

缺陷:

对突发流量的处理不足灵巧:虽然漏桶算法可以平滑突发流量,但是在某些情形下,我们可能希望能够快速处理突发流量。
在这种情形下,漏桶算法可能就不足灵巧了。
无法动态调度流量:漏桶算法的流出速率是固定的,无法根据系统的实际情形动态调度。
可能会导致流量摧残浪费蹂躏:如果输入流量小于漏桶的流出速率,那么漏桶的流出速率就会被摧残浪费蹂躏。
如果输入流量持续大于漏桶的流出速率,那么漏桶会一贯满,新的要求会被丢弃,可能会导致做事质量低落。
3.4 令牌桶限流3.4.1 算法先容

令牌桶算法是实现限流是一种常见的思路,用于限定要求的速率。
它可以确保系统在高负载情形下仍能供应稳定的做事,并防止突发流量对系统造成过载。
最为常用的 Google 的 Java 开拓工具包 Guava 中的限流工具类 RateLimiter 便是令牌桶的一个实现。
令牌桶算法基于一个令牌桶的观点,个中令牌以固定的速率产生,并放入桶中。
每个令牌代表一个要求的容许。
当要求到达时,须要从令牌桶中获取一个令牌才能通过。
如果令牌桶中没有足够的令牌,则要求被限定或丢弃。
令牌桶算法的实现步骤如下:

初始化一个令牌桶,包括桶的容量和令牌产生的速率。
持续以固定速率产生令牌,并放入令牌桶中,直到桶满为止。
当要求到达时,考试测验从令牌桶中获取一个令牌。
如果令牌桶中有足够的令牌,则要求通过,并从令牌桶中移除一个令牌。
如果令牌桶中没有足够的令牌,则要求被限定或丢弃。

3.4.2 代码实现

package mainimport ( "fmt" "sync" "time")// TokenBucket 表示一个令牌桶。
type TokenBucket struct { rate float64 // 令牌添加到桶中的速率。
capacity float64 // 桶的最大容量。
tokens float64 // 当前桶中的令牌数量。
lastUpdate time.Time // 上次更新令牌数量的韶光。
mu sync.Mutex // 互斥锁,确保线程安全。
}// NewTokenBucket 创建一个新的令牌桶,给定令牌添加速率和桶的容量。
func NewTokenBucket(rate float64, capacity float64) TokenBucket { return &TokenBucket{ rate: rate, capacity: capacity, tokens: capacity, // 初始时,桶是满的。
lastUpdate: time.Now(), }}// Allow 检讨是否可以从桶中移除一个令牌。
如果可以,它移除一个令牌并返回 true。
// 如果不可以,它返回 false。
func (tb TokenBucket) Allow() bool { tb.mu.Lock() defer tb.mu.Unlock() // 根据经由的韶光打算要添加的令牌数量。
now := time.Now() elapsed := now.Sub(tb.lastUpdate).Seconds() tokensToAdd := elapsed tb.rate tb.tokens += tokensToAdd if tb.tokens > tb.capacity { tb.tokens = tb.capacity // 确保令牌数量不超过桶的容量。
} if tb.tokens >= 1.0 { tb.tokens-- tb.lastUpdate = now return true } return false}func main() { tokenBucket := NewTokenBucket(2.0, 3.0) for i := 1; i <= 10; i++ { now := time.Now().Format("15:04:05") if tokenBucket.Allow() { fmt.Printf(now+" 第 %d 个要求通过\n", i) } else { // 如果不能移除一个令牌,要求被谢绝。
fmt.Printf(now+" 第 %d 个要求被限流\n", i) } time.Sleep(200 time.Millisecond) }}

实行结果:

3.4.3 优缺陷

优点:

平滑流量:令牌桶算法可以平滑突发流量,使得突发流量在一段韶光内均匀地分布,避免了流量的溘然高峰对系统的冲击。
灵巧性:令牌桶算法可以通过调度令牌天生速率和桶的大小来灵巧地掌握流量。
许可突发流量:由于令牌桶可以积累一定数量的令牌,因此在流量溘然增大时,如果桶中有足够的令牌,可以应对这种突发流量。

缺陷:

实现繁芜:比较于其他一些限流算法(如漏桶算法),令牌桶算法的实现轻微繁芜一些,须要掩护令牌的天生和花费。
须要精确的韶光掌握:令牌桶算法须要根据韶光来天生令牌,因此须要有精确的韶光掌握。
如果系统的韶光掌握禁绝确,可能会影响限流的效果。
可能会有资源摧残浪费蹂躏:如果系统的流量持续低于令牌天生的速率,那么桶中的令牌可能会一贯积累,造成资源的摧残浪费蹂躏。
3.5 四种基本算法的比拟

4 分布式限流

单机限流指针对单一做事器的情形,通过限定单台做事器在单位韶光内处理的要求数量,防止做事器过载。
常见的限流算法上文已先容,其优点在于实现大略,效率高,效果明显。
随着微做事架构的遍及,系统的做事常日会支配在多台做事器上,此时就须要分布式限流来担保全体系统的稳定性。
接下本文会先容几种常见的分布式限流技能方案:

4.1 基于中央化的限流方案4.1.1 方案事理

通过一个中央化的限流器来掌握所有做事器的要求。
实现办法:

选择一个中央化的组件,例如—Redis。
定义限流规则,例如:可以设置每秒钟许可的最大要求数(QPS),并将这个值存储在Redis中。
对付每个要求,做事器须要先向Redis要求令牌。
如果获取到令牌,解释要求可以被处理;如果没有获取到令牌,解释要求被限流,可以返回一个缺点信息或者稍后重试。
4.1.2 代码实现

package mainimport ( "context" "fmt" "go.uber.org/atomic" "sync" "git.code.oa.com/pcg-csd/trpc-ext/redis")type RedisClient interface { Do(ctx context.Context, cmd string, args ...interface{}) (interface{}, error)}// Client 数据库type Client struct { client RedisClient // redis 操作 script string // lua脚本}// NewBucketClient 创建redis令牌桶func NewBucketClient(redis RedisClient) Client { helper := redis return &Client{ client: helper, script: ` -- 令牌桶限流脚本 -- KEYS[1]: 桶的名称 -- ARGV[1]: 桶的容量 -- ARGV[2]: 令牌产生速率 local bucket = KEYS[1] local capacity = tonumber(ARGV[1]) local tokenRate = tonumber(ARGV[2]) local redisTime = redis.call('TIME') local now = tonumber(redisTime[1]) local tokens, lastRefill = unpack(redis.call('hmget', bucket, 'tokens', 'lastRefill')) tokens = tonumber(tokens) lastRefill = tonumber(lastRefill) if not tokens or not lastRefill then tokens = capacity lastRefill = now else local intervalsSinceLast = (now - lastRefill) tokenRate tokens = math.min(capacity, tokens + intervalsSinceLast) end if tokens < 1 then return 0 else redis.call('hmset', bucket, 'tokens', tokens - 1, 'lastRefill', now) return 1 end `, }}// 获取令牌,获取成功则立即返回true,否则返回falsefunc (c Client) isAllowed(ctx context.Context, key string, capacity int64, tokenRate int64) (bool, error) { result, err := redis.Int(c.client.Do(ctx, "eval", c.script, 1, key, capacity, tokenRate)) if err != nil { fmt.Println("Redis 实行缺点:", err) return false, err } return result == 1, nil}// 调用检测func main() { c := NewBucketClient(redis.GetPoolByName("redis://127.0.0.1:6379")) gw := sync.WaitGroup{} gw.Add(120) count := atomic.Int64{} for i := 0; i < 120; i++ { go func(i int) { defer gw.Done() status, err := c.isAllowed(context.Background(), "test", 100, 10) if status { count.Add(1) } fmt.Printf("go %d status:%v error: %v\n", i, status, err) }(i) } gw.Wait() fmt.Printf("allow %d\n\n", count.Load())}

实行结果:

4.1.3 存在的问题性能瓶颈:由于所有的要求都须要经由Redis,因此Redis可能成为全体系统的性能瓶颈。
为理解决这个问题,可以考虑利用Redis集群来提高性能,或者利用更高性能的硬件。
单点故障:如果Redis涌现故障,全体系统的限流功能将受到影响。
为理解决这个问题,可以考虑利用Redis的主从复制或者哨兵模式来实现高可用。
网络带宽:Redis是一个基于网络通信的内存数据库,因此网络带宽是其性能的一个关键成分。
如果网络带宽有限,可能会导致要求的传输速率变慢,从而影响Redis的性能。

redis官方性能测试图

4.2 基于负载均衡的分布式限流方案4.2.1 方案事理

可以看到中央化限流方案的存在较高的单点故障风险,且带宽瓶颈比较严重。
在这个根本上本文结合本地缓存单机限流和负载均衡设计了一个新的分布式限流方案。
详细方案如下:

利用负载均衡器或分布式做事创造(北极星即可做到),将要求均匀地分发到每个机器上。
这确保了每个机器都能处理一部分要求。
在每个机器上掩护本机的限流状态,实现本地缓存单机限流的逻辑。
利用令牌桶限流算法,在每个机器上独立地进行限流掌握。
每秒钟处理的要求数、令牌桶的令牌数量等。
根据本地限流状态,对到达的要求进行限流判断。
准备相应的动态调度方案,可以根据每个机器的实际负载情形,动态地调度限流参数。
例如,如果一个机器的CPU或内存利用率过高,你可以降落这个机器的限流阈值,减少这个机器的要求处理量。
反之,如果一个机器的资源利用率较低,提高这个机器的限流阈值,增加这个机器的要求处理量。
4.2.2 存在的问题本地缓存:这个方案对本地缓存的哀求较高,须要自己根据业务逻辑决议本地缓存的淘汰策略和缓存容量限定等风险点。
限流精度:本地缓存单机限流的精度受限于每个做事实例的资源和配置。
这可能导致限流策略无法精确地适应全体系统的流量变革,无法灵巧地调度限流规则。
要求负载均衡器的单点故障。
动态扩缩容的适应性:当系统须要动态扩展或缩容时,该方案可能须要额外的配置和和调度,以确保新加入或移除的做事实例能够精确地参与限流和要求均衡。
4.3 基于分布式折衷做事的限流4.3.1 方案事理

利用ZooKeeper或者etcd等分布式折衷做事来实现限流。
每台做事器都会向分布式折衷做事申请令牌,只有获取到令牌的要求才能被处理。
基本方案:

初始化令牌桶:在ZooKeeper中创建一个节点,节点的数据代表令牌的数量。
初始时,将数据设置为令牌桶的容量。
申请令牌:当一个要求到达时,做事器首先向ZooKeeper申请一个令牌。
这可以通过获取节点的分布式锁,然后将节点的数据减1实现。
如果操作成功,解释申请到了令牌,要求可以被处理;如果操作失落败,解释令牌已经用完,要求须要被谢绝或者等待。
开释令牌:当一个要求处理完毕时,做事器须要向ZooKeeper开释一个令牌。
这可以通过获取节点的分布式锁,然后将节点的数据加1实现。
补充令牌:可以设置一个定时任务,定期向ZooKeeper中的令牌桶补充令牌。
补充的频率和数量可以根据系统的负载情形动态调度。
4.3.2 存在的问题

这个方案的优点是可以实现精确的全局限流,并且可以避免单点故障。
但是,这个方案的缺陷是实现繁芜,且对ZooKeeper的性能有较高的哀求。
如果ZooKeeper无法处理大量的令牌申请和开释操作,可能会成为系统的瓶颈。

5 总结

总之,没有最好的方案,只有得当的方案。
在选择得当的限流方案时,我们须要考虑多种成分,包括系统的需求、现有的技能栈、系统的负载情形以及底层系统的性能等。
理解每种方案的事情事理和特性,以便在实际运用中做出最佳的选择。

一个好的限流设计必须要考虑到业务的特性和需求,同时具备以下六点:

多级限流:除了主备复制的限流做事,可以考虑实现多级限流策略。
例如,可以在运用层、做事层和数据层都设置限流,这样可以更好地防止系统过载。
动态阈值调度:我们可以根据系统的实时负载情形动态调度限流策略。
例如,当系统负载较低时,我们可以放脱期流策略;当系统负载较高时,我们可以收紧限流策略。
灵巧维度:限流策略该当能够根据不同的业务场景进行调度。
除了接口,设备,ip,账户id等维度外,我们还可以考虑更细粒度的限流。
例如,我们可以根据用户的行为模式进行限流,这样可以更好地防止恶意用户的攻击。
解耦性:限流该当作为一个根本做事,与详细的业务逻辑分离。
这样,当业务逻辑发生变革时,不须要修正限流做事的代码,只须要调度限流策略即可。
容错性:限流做事该当具有高可用性,但是如果涌现问题,业务该当有备选方案(熔断、降级)。
这可能包括利用备用的限流做事,或者根据业务的敏感性决定是否放行要求。
监控和报警:对限流策略应进行实时监控,并设置报警机制。
当限流策略触发时,可立即收到报警,以便我们可以及时地处理问题。

限流是担保系统稳定和高效运行的主要手段,但它并不是唯一的办理方案。
我们还须要考虑其他的系统设计和优化手段,例如负载均衡、缓存、异步处理等(面对爆量,扩容永久是最好的办法,除了贵!
)。
这些手段相互合营,才能构建出一个既能应对高并发要求,又能担保做事质量的系统。

标签:

相关文章

php测验计时技巧_php实现在线考试系统

文章正文效果图如下:题库首页展示试题在线测试分数统计通过该小项目目的是练习PHP根本知识,通过对数组操作实现考题信息存储加载,引入...

网站建设 2024-12-14 阅读0 评论0