写点什么

利用 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
用户头像

关注

还未添加个人签名 2019-12-21 加入

还未添加个人简介

评论

发布
暂无评论
利用golang特性,设计一个QPS为500的服务器_Go 语言_宇_InfoQ写作社区