服务器限流可以降低服务器的压力,如果我们将QPS限定在1000,那么就可以保证服务器每秒处理的请求不会超过1000,超过1000的那一部分直接返回一个错误信息就行了,不然他走业务流程。
思路一
假设我们需要限流QPS为100,我们可以实现预备好100个牌子,请求到了先尝试拿牌子,拿到牌子后才能进入业务流程,否则就直接返回错误信息。同时我们开启一个协程去不停的补充牌子。
package main
import (
"fmt"
"time"
)
func main() {
go addRemain()
for {
time.Sleep(time.Microsecond * 500)
fmt.Println(getData())
}
}
var remain = 100
func getData() string {
if remain > 0 {
remain--
} else {
return "被限流了"
}
return "message"
}
func addRemain() {
for {
time.Sleep(time.Millisecond * 10)
if remain >= 100 {
continue
}
remain += 1
}
}
思路二
上面这种方法会导致addRemain()在1秒内被多次执行,效率比较差,也不够优雅。其实我们可以在请求到来的时候才顺便添加牌子,根据距离上一次请求过去的时间,来决定应该添加多少个牌子,这样子就降低了addRemain协程的无效执行次数。
或者说,可以在请求处理结束后,归还牌子,这样子就不需要自己手动添加令牌了,这样子的话其实就不是限制QPS了,而是限制最大并发数,不过也没问题,我们限制QPS的最终目的不就是为了限制最大并发量吗。
思路三
根据上一次请求的时间戳,决定下一次请求应该在什么时间戳,如果还没到时间就来请求,就拦截,否则放心,更新下一次时间戳。
package main
import (
"fmt"
"time"
)
const MAX_QPS int64 = 500 // 每秒访问量
const MAX_NPQ int64 = 1e9 / MAX_QPS // 两次访问之间应该隔多少纳秒
var nextTimestamp int64
func main() {
nextTimestamp = time.Now().UnixNano()
count := 0
for {
if getData() == "message" {
count++
fmt.Printf("成功请求了%d次\n", count)
}
}
}
func getData() string {
now := time.Now().UnixNano()
if now < nextTimestamp {
return "被限流了"
}
//更新下一次时间戳
nextTimestamp = now + MAX_NPQ
return "message"
}
我们运行程序,发现成功请求次数大概每秒打印500次左右(实际上远比500小),然后将QPS改成5,发现打印频率明显下降。说明我们的限流成功了。