各类限频原理
网上有很多讲解限频原理以及限频原因的,限频常用在接口、服务的流量、并发上,主要是为了合理使用后端资源,防止后端被压垮,雪崩等等。
实现方法
这里使用使用go的ring(环形队列)实现滑动窗口
实现代码
package main
import (
"fmt"
"net"
"os"
"container/ring"
"sync/atomic"
"sync"
"time"
)
var (
limitCount int = 10 // 6s限频
limitBucket int = 6 // 滑动窗口个数
curCount int32 = 0 // 记录限频数量
head *ring.Ring // 环形队列(链表)
)
func main() {
tcpAddr, err := net.ResolveTCPAddr("tcp4", "0.0.0.0:9090") //获取一个tcpAddr
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr) //监听一个端口
checkError(err)
defer listener.Close()
// 初始化滑动窗口
head = ring.New(limitBucket)
for i := 0; i < limitBucket; i++ {
head.Value = 0
head = head.Next()
}
// 启动执行器
go func() {
timer := time.NewTicker(time.Second * 1)
for range timer.C { // 定时每隔1秒刷新一次滑动窗口数据
subCount := int32(0 - head.Value.(int))
newCount := atomic.AddInt32(&curCount, subCount)
arr := [6]int{}
for i := 0; i < limitBucket; i++ { // 这里是为了方便打印
arr[i] = head.Value.(int)
head = head.Next()
}
fmt.Println("move subCount,newCount,arr", subCount, newCount,arr)
head.Value = 0
head = head.Next()
}
}()
for {
conn, err := listener.Accept() // 在此处阻塞,每次来一个请求才往下运行handle函数
if err != nil {
fmt.Println(err)
continue
}
go handle(&conn) // 起一个单独的协程处理,有多少个请求,就起多少个协程,协程之间共享同一个全局变量limiting,对其进行原子操作。
}
}
func handle(conn *net.Conn) {
defer (*conn).Close()
n := atomic.AddInt32(&curCount, 1)
//fmt.Println("handler n:", n)
if n > int32(limitCount) { // 超出限频
atomic.AddInt32(&curCount, -1) // add 1 by atomic,业务处理完毕,放回令牌
(*conn).Write([]byte("HTTP/1.1 404 NOT FOUND\r\n\r\nError, too many request, please try again."))
} else {
mu := sync.Mutex{}
mu.Lock()
pos := head.Prev()
val := pos.Value.(int)
val++
pos.Value = val
mu.Unlock()
time.Sleep(1 * time.Second) // 假设我们的应用处理业务用了1s的时间
(*conn).Write([]byte("HTTP/1.1 200 OK\r\n\r\nI can change the world!")) // 业务处理结束后,回复200成功。
}
}
// 异常报错的处理
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
压测试
另外起一个终端,用golang的boom来做压测。要提前安装boom工具
go get github.com/rakyll/hey
go install github.com/rakyll/hey
进行压测试:
PS D:\Project\go\bin> .\hey.exe -c 6 -n 300 -q 6 -t 80 http://localhost:9090
压测试输出
move subCount,newCount,arr 0 0 [0 0 0 0 0 0]
move subCount,newCount,arr 0 0 [0 0 0 0 0 0]
move subCount,newCount,arr 0 6 [0 0 0 0 0 6]
move subCount,newCount,arr 0 10 [0 0 0 0 6 4]
move subCount,newCount,arr 0 10 [0 0 0 6 4 0]
move subCount,newCount,arr 0 10 [0 0 6 4 0 0]
move subCount,newCount,arr 0 10 [0 6 4 0 0 0]
move subCount,newCount,arr -6 4 [6 4 0 0 0 0]
move subCount,newCount,arr -4 6 [4 0 0 0 0 6]
move subCount,newCount,arr 0 10 [0 0 0 0 6 4]
move subCount,newCount,arr 0 10 [0 0 0 6 4 0]
move subCount,newCount,arr 0 10 [0 0 6 4 0 0]
move subCount,newCount,arr 0 10 [0 6 4 0 0 0]
move subCount,newCount,arr -6 4 [6 4 0 0 0 0]
move subCount,newCount,arr -4 3 [4 0 0 0 0 3]
move subCount,newCount,arr 0 3 [0 0 0 0 3 0]
move subCount,newCount,arr 0 3 [0 0 0 3 0 0]
move subCount,newCount,arr 0 3 [0 0 3 0 0 0]
move subCount,newCount,arr 0 3 [0 3 0 0 0 0]
move subCount,newCount,arr -3 0 [3 0 0 0 0 0]
move subCount,newCount,arr 0 0 [0 0 0 0 0 0]
move subCount,newCount,arr 0 0 [0 0 0 0 0 0]
查看其中数组可以知道每一秒此时滑动窗口的限频值,以及变化。
压测试客户端输出
可以看出压测试服务在12.6秒进行了300次http请求其中有23次正确响应成功,限频测试ok
D:\Project\go\bin> .\hey.exe -c 6 -n 300
-q 6 -t 80 http://localhost:9090
Summary:
Total: 12.6701 secs
Slowest: 1.0163 secs
Fastest: 0.0008 secs
Average: 0.0789 secs
Requests/sec: 23.6779
Response time histogram:
0.001 [1] |
0.102 [276] |■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■
■■■■■
0.204 [0] |
0.305 [0] |
0.407 [0] |
0.509 [0] |
0.610 [0] |
0.712 [0] |
0.813 [0] |
0.915 [0] |
1.016 [23] |■■■
Latency distribution:
10% in 0.0012 secs
25% in 0.0014 secs
50% in 0.0016 secs
75% in 0.0019 secs
90% in 0.0071 secs
95% in 1.0016 secs
99% in 1.0162 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0020 secs, 0.0008 secs, 1.0163 secs
DNS-lookup: 0.0012 secs, 0.0001 secs, 0.0150 secs
req write: 0.0000 secs, 0.0000 secs, 0.0001 secs
resp wait: -67.5636 secs, -2902.3027 secs, 1.0014 secs
resp read: 0.0000 secs, 0.0000 secs, 0.0003 secs
Status code distribution:
[200] 23 responses
[404] 277 responses