Golang:分布式⾼并发场景,服务限流实现⽅案
服务限流场景
在⾼并发⼤流量系统中,由于并发⼤造成服务资源不⾜,负载过⾼,进⽽引发致⼀系列问题,这⾥的流量⼀般都是突发性的,由于系统准备不⾜,很难短期扩容来应对 ,进⾏限流是最常⽤的⼿段,所以说限流也是服务稳定性治理重要的⼿段。
限流可能发⽣在多个层⾯:
1.⽤户⽹络层:突发的流量场景如热点事件流量(秒杀事件、热门抢购,微博热搜),恶意刷流,竞对爬⾍等。
2.内部应⽤层:上游服务的异常调⽤,脚本异常请求,失败重试策略造成流量突发。
实现限流⽅案
常⽤的限流⽅法主要有三种:计数器算法,漏⽃桶算法,令牌桶算法。
1.计算器限流
1.1 实现原理
设计限流条件,如根据⽤户id/商户id/IP/UUID+请求url作为限流对象,对限流对象的每次流量访问进⾏全局计数,设置限流阈值(1000次/秒,10000/分钟),如果统计时间窗⼝期内达到阈值就进⾏限流。
对单机限流来说,使⽤全局内存计数即可,但对分布式系统需要有⼀个公共存储计数,redis是最佳存储⽅案,且redis的incr能保障原⼦性操作。
1.2 代码实现
//@param key string object for rate limit such as uid/ip+url
//@param fillInterval time.Duration such as 1*time.Second
//@param limitNum max int64 allowed number per fillInterval
//@return whether reach rate limit, false means reach.
func fixedWindowRateLimit(key string, fillInterval time.Duration, limitNum int64) bool {
//current tick time window
tick := int64(time.Now().Unix() / int64(fillInterval.Seconds()))
currentKey := fmt.Sprintf("%s_%d_%d_%d", key, fillInterval, limitNum, tick)
startCount := 0
_, err := client.SetNX(currentKey, startCount, fillInterval).Result()
if err != nil {
panic(err)
}
//number in current time window
quantum, err := client.Incr(currentKey).Result()
if err != nil {
panic(err)
}
if quantum > limitNum {
return false
}
return true
}
完整代码参见:
测试代码:
func test1() {
for i := 0; i < 10; i++ {
go func() {
rs := fixedWindowRateLimit("test1", 1*time.Second, 5)
fmt.Println("result is:", rs)
}()
}
}
测试执⾏结果:
eval是做什么的根据执⾏结果可以看到,1秒中有10个请求,只有5个通过,另5个被限流返回false。
这个代码实现的是固定时间窗⼝,有⼀个问题,当流量在上⼀个时间窗⼝下半段和下⼀个时间窗⼝上半段集中爆发,那么这两段组成的时间窗⼝内流量是会超过limit限制的。
测试代码如下,拉长时间窗⼝为1分钟,1分钟限流5个,前30s没流量,之后每10s⼀个请求:
func test2() {
fillInteval := 1 * time.Minute
var limitNum int64 = 5
waitTime := 30
fmt.Printf("time range from 0 to %d\n", waitTime)
time.Sleep(time.Duration(waitTime) * time.Second)
for i := 0; i < 10; i++ {
fmt.Printf("time range from %d to %d\n", i*10+waitTime, (i+1)*10+waitTime)
rs := fixedWindowRateLimit("test2", fillInteval, limitNum)
fmt.Println("result is:", rs)
time.Sleep(10 * time.Second)
}
}
输出如下
根据执⾏结果可以看到,0-60s总共4个true满⾜1分钟窗⼝5个,60-120总共5个true,1个false满⾜限流,但30-90这1分钟的时间窗总共6个true,超过5个限制。
1.3 ⽅案改进:使⽤滑动窗⼝
//segmentNum split inteval time into smaller segments
func slidingWindowRatelimit(key string, fillInteval time.Duration, segmentNum int64, limitNum int64) bool {
segmentInteval := fillInteval.Seconds() / float64(segmentNum)
tick := float64(time.Now().Unix()) / segmentInteval
currentKey := fmt.Sprintf("%s_%d_%d_%d_%f", key, fillInteval, segmentNum, limitNum, tick)
startCount := 0
_, err := client.SetNX(currentKey, startCount, fillInteval).Result()
if err != nil {
panic(err)
}
quantum, err := client.Incr(currentKey).Result()
if err != nil {
panic(err)
}
//add in the number of the previous time
for tickStart := segmentInteval; tickStart < fillInteval.Seconds(); tickStart += segmentInteval {
tick = tick - 1
preKey := fmt.Sprintf("%s_%d_%d_%d_%f", key, fillInteval, segmentNum, limitNum, tick)
val, err := client.Get(preKey).Result()
if err != nil {
val = "0"
}
num, err := strconv.ParseInt(val, 0, 64)
quantum = quantum + num
if quantum > limitNum {
client.Decr(currentKey).Result()
return false
}
}
return true
}
完整代码参见:
滑动窗⼝增加⼀个参数segmentNum,表⽰把固定窗⼝再分成⼏段,如上图的0-10 ... 50-60,把1分钟分成6段,代码执⾏结果如
下,30-90,40-100,任意1分钟滑动窗⼝都满⾜5个最⼤限制。
1.4 计数器的适⽤场景
适⽤于做API限流,⽐如对外提供ip定位查询服务api,天⽓查询api等,可以根据ip做粒度控制,防⽌恶意刷接⼝造成异常,也适⽤于提供API查询服务做配额限制,⼀般限流后会对请求做丢弃处理。
局限:窗⼝算法对于流量限制是定速的,对细粒度时间控制突发流量控制能⼒就有限了。
2.漏⽃桶限流
2.1 实现原理
漏⽃桶形象⽐喻为⼀个滤⽔漏⽃,⽔滴(请求)可能很快把漏⽃填满(流量流⼊),漏⽃出来的⽔滴(流量处理)是匀速固定的,桶满则新进⼊⽔滴(请求)会被限流。
常⽤队列⽅式来实现,请求到达后放⼊队列中,有⼀个处理器从队列匀速取出进⾏处理。当桶满了,新流量过来会被限流。
uber提供了基于漏⽃桶的算法实现可以参考:
另外:redis4.0提供了限流模块,redis-cell,该模块使⽤漏⽃算法,并提供原⼦限流指令。
cl.throttle key capacity limitNum fillInteval
2.2 漏⽃桶适⽤场景
漏⽃桶更像是对流量进⾏整形Traffic Shaping,所有流量过来都要进⾏排队,依次出去,可⽤于做⼀些论坛博客发帖频率限制。
相对于计数器限流,达到限流后该时间窗⼝会丢弃⼀切请求,漏⽃在桶满后,由于还会有持续流出,新到达请求还有机会流⼊。
局限:由于出⼝处理速率是匀速的,短时有⼤量突发请求,即使负载压⼒不⼤,请求仍需要在队列等待处理。
3.令牌桶限流
3.1 实现原理
令牌桶算法是⼀个桶,匀速向桶⾥放令牌,控制桶最⼤容量(令牌最⼤数)和放⼊令牌速率(⽣成令牌/秒)。请求从桶中拿令牌,拿到令牌可以通过,拿不到就被限流了。
当访问量⼩时,令牌桶可以积累令牌到桶满,⽽当短时突发流量,积累的令牌能保障⼤量请求可以⽴刻拿到令牌,令牌⽤完了,请求会依赖于新令牌申请速度,这时会退化成类似漏⽃桶算法。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。