利用 golang 特性,设计一个 QPS 为 500 的服务器
作者:宇
- 2025-08-17 北京
本文字数:1967 字
阅读完需:约 6 分钟
思路
看到这个题目的时候,第一时间就想到了限流器的系统设计,基础的限流器算法有两种,一种是漏桶算法,一种是令牌桶算法。
漏桶算法:
当一个请求到达的时候,系统会先检查桶是否已满。如果没有,就将请求添加到队列;否则丢弃请求。
定期从队列中取出请求并进行处理。
令牌桶算法:
令牌桶是一个有预定义容量的容器。令牌按照预定的速率被放入桶中,一旦桶被装满,就不再向里面添加令牌。
每个请求消耗一个令牌。当一个请求到达时,我们检查桶里有没有足够的令牌。
如果有的话,那么就取出一个令牌,然后这个请求就可以通过。
如果没有足够的令牌,那么 这个请求将被丢弃。
漏桶算法
package main
import (
"fmt"
"net/http"
"time"
)
// 漏桶容量
const capacity = 100
var ch = make(chan struct{}, capacity)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case ch <- struct{}{}:
next(w, r)
default:
http.Error(w, "rate limit", http.StatusTooManyRequests)
}
})
}
func main() {
go func() {
ticker := time.NewTicker(time.Second / capacity)
defer ticker.Stop()
for range ticker.C {
select {
case <-ch: // 漏掉1个请求
default:
}
}
}()
mux := http.NewServeMux()
mux.HandleFunc("/", rateLimitMiddleware(handler))
http.ListenAndServe(":8080", mux)
}
复制代码

令牌桶算法
package main
import (
"fmt"
"net/http"
"time"
)
// 令牌桶容量
const capacity = 100
var bucket = make(chan struct{}, capacity)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case <-bucket:
next(w, r)
default:
http.Error(w, "rate limit", http.StatusTooManyRequests)
}
})
}
func main() {
go func() {
ticker := time.NewTicker(time.Second / capacity)
defer ticker.Stop()
for range ticker.C {
select {
case bucket <- struct{}{}: // 放入一个令牌
default:
}
}
}()
mux := http.NewServeMux()
mux.HandleFunc("/", rateLimitMiddleware(handler))
http.ListenAndServe(":8080", mux)
}
复制代码
测量脚本
package main
import (
"flag"
"fmt"
"net/http"
"sync"
"time"
)
var (
url = flag.String("url", "http://localhost:8080", "目标URL")
qps = flag.Int("qps", 500, "QPS")
duration = flag.Int("duration", 5, "Duration")
concurrency = flag.Int("concurrency", 10, "并发数")
)
func main() {
flag.Parse()
totalReq := *qps * *duration
var (
successCount int
failCount int
totalTime time.Duration
wg sync.WaitGroup
mu sync.Mutex
)
taskChan := make(chan struct{}, *concurrency)
for i := 0; i < *concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for range taskChan {
start := time.Now()
resp, err := http.Get(*url)
elapsed := time.Since(start)
mu.Lock()
totalTime += elapsed
if err != nil || resp.StatusCode != 200 {
failCount++
} else {
successCount++
}
mu.Unlock()
if resp != nil {
resp.Body.Close()
}
}
}()
}
ticker := time.NewTicker(time.Second / time.Duration(*qps))
defer ticker.Stop()
done := make(chan struct{})
go func() {
time.Sleep(time.Duration(*duration) * time.Second)
done <- struct{}{}
}()
count := 0
for {
select {
case <-ticker.C:
if count >= totalReq {
continue
}
taskChan <- struct{}{}
count++
case <-done:
ticker.Stop()
close(taskChan)
wg.Wait()
fmt.Printf("测试结果:\n")
fmt.Printf("总请求数: %d\n", totalReq)
fmt.Printf("成功数: %d\n", successCount)
fmt.Printf("失败数: %d\n", failCount)
if successCount > 0 {
fmt.Printf("平均响应时间: %v\n", totalTime/time.Duration(successCount))
} else {
fmt.Println("平均响应时间: N/A (无成功请求)")
}
fmt.Printf("吞吐量: %.2f QPS\n", float64(successCount)/float64(*duration))
return
}
}
}
复制代码
划线
评论
复制
发布于: 刚刚阅读数: 3
版权声明: 本文为 InfoQ 作者【宇】的原创文章。
原文链接:【http://xie.infoq.cn/article/920664250b28773b48aea2a24】。文章转载请联系作者。

宇
关注
还未添加个人签名 2019-12-21 加入
还未添加个人简介
评论