Go语言Ratelimit服务流量限制

  • 内容
  • 评论
  • 相关

计算机程序可依据其瓶颈分为磁盘 IO 瓶颈型,CPU 计算瓶颈型,⽹络带宽瓶颈型,分布式场景下有时候也会外部系统⽽导致⾃身瓶颈。

Web 系统打交道最多的是⽹络,⽆论是接收,解析⽤户请求,访问存储,还是把响应数据返回给⽤户,都是要⾛⽹络的。在没有 epoll/kqueue 之类的系统提供的 IO 多路复⽤接⼝之前,多个核⼼的现代计算机最头痛的是 C10k 问题,C10k 问题会导致计算机没有办法充分利⽤ CPU 来处理更多的⽤户连接,进⽽没有办法通过优化程序提升CPU利⽤率来处理更多的请求。

⾃从 Linux 实现了 epoll,FreeBSD 实现了 kqueue ,这个问题基本解决了,我们可以借助内核提供的 API 轻松解决当年的 C10k 问题,也就是说如今如果你的程序主要是和⽹络打交道,那么瓶颈⼀定在⽤户程序⽽不在操作系统内核。

随着时代的发展,编程语⾔对这些系统调⽤⼜进⼀步进⾏了封装,如今做应⽤层开发,⼏乎不会在程序中看到 epoll 之类的字眼,⼤多数时候我们就只要聚焦在业务逻辑上就好。

Go语言的 net 库针对不同平台封装了不同的 syscall API,http 库⼜是构建在 net 库之上,所以在 Go语⾔中我们可以借助标准库,很轻松地写出⾼性能的 http 服务,下⾯是⼀个简单的 hello world 服务的代码:

package main

import (
    "io"
    "log"
    "net/http"
)

func sayhello(wr http.ResponseWriter, r *http.Request) {
    wr.WriteHeader(200)
    io.WriteString(wr, "hello world")
}
func main() {
    http.HandleFunc("/", sayhello)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}

我们需要衡量⼀下这个 Web 服务的吞吐量,再具体⼀些,实际上就是接⼝的 QPS。借助 wrk,在家⽤电脑 Macbook Pro 上对这个 hello world 服务进⾏基准测试,Mac 的硬件情况如下:

CPU: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
Core: 2
Threads: 4
Graphics/Displays:
    Chipset Model: Intel Iris Graphics 6100
        Resolution: 2560 x 1600 Retina
    Memory Slots:
        Size: 4 GB
        Speed: 1867 MHz
        Size: 4 GB
        Speed: 1867 MHz
    Storage:
        Size: 250.14 GB (250,140,319,744 bytes)
        Media Name: APPLE SSD SM0256G Media
        Size: 250.14 GB (250,140,319,744 bytes)
        Medium Type: SSD

测试结果:

~ ❯❯❯ wrk -c 10 -d 10s -t10 http://localhost:9090
Running 10s test @ http://localhost:9090
    10 threads and 10 connections
    Thread Stats Avg Stdev Max +/- Stdev
        Latency 339.99us 1.28ms 44.43ms 98.29%
        Req/Sec 4.49k 656.81 7.47k 73.36%
    449588 requests in 10.10s, 54.88MB read
Requests/sec: 44513.22
Transfer/sec: 5.43MB

~ ❯❯❯ wrk -c 10 -d 10s -t10 http://localhost:9090
Running 10s test @ http://localhost:9090
    10 threads and 10 connections
    Thread Stats Avg Stdev Max +/- Stdev
        Latency 334.76us 1.21ms 45.47ms 98.27%
        Req/Sec 4.42k 633.62 6.90k 71.16%
    443582 requests in 10.10s, 54.15MB read
Requests/sec: 43911.68
Transfer/sec: 5.36MB

~ ❯❯❯ wrk -c 10 -d 10s -t10 http://localhost:9090
Running 10s test @ http://localhost:9090
    10 threads and 10 connections
    Thread Stats Avg Stdev Max +/- Stdev
        Latency 379.26us 1.34ms 44.28ms 97.62%
        Req/Sec 4.55k 591.64 8.20k 76.37%
    455710 requests in 10.10s, 55.63MB read
Requests/sec: 45118.57
Transfer/sec: 5.51MB

多次测试的结果在 4 万左右的 QPS 浮动,响应时间最多也就是 40ms 左右,对于⼀个 Web 程序来说,这已经是很不错的成绩了,我们只是照抄了别⼈的示例代码,就完成了⼀个⾼性能的 hello world 服务器,是不是很有成就感?

这还只是家⽤ PC,线上服务器⼤多都是 24 核⼼起,32G 内存 +,CPU 基本都是 Intel i7。所以同样的程序在服务器上运⾏会得到更好的结果。

这⾥的 hello world 服务没有任何业务逻辑。真实环境的程序要复杂得多,有些程序偏⽹络 IO 瓶颈,例如⼀些 CDN 服务、Proxy 服务;有些程序偏 CPU/GPU 瓶颈,例如登陆校验服务、图像处理服务;有些程序瓶颈偏磁盘,例如专⻔的存储系统,数据库。

不同的程序瓶颈会体现在不同的地⽅,这⾥提到的这些功能单⼀的服务相对来说还算容易分析。如果碰到业务逻辑复杂代码量巨⼤的模块,其瓶颈并不是三下五除⼆可以推测出来的,还是需要从压⼒测试中得到更为精确的结论。

对于 IO/Network 瓶颈类的程序,其表现是⽹卡 / 磁盘 IO 会先于 CPU 打满,这种情况即使优化 CPU 的使⽤也不能提⾼整个系统的吞吐量,只能提⾼磁盘的读写速度,增加内存⼤⼩,提升⽹卡的带宽来提升整体性能。

⽽ CPU 瓶颈类的程序,则是在存储和⽹卡未打满之前 CPU 占⽤率提前到达 100%,CPU 忙于各种计算任务,IO 设备相对则较闲。

⽆论哪种类型的服务,在资源使⽤到极限的时候都会导致请求堆积,超时,系统 hang 死,最终伤害到终端⽤户。对于分布式的 Web 服务来说,瓶颈还不⼀定总在系统内部,也有可能在外部。

⾮计算密集型的系统往往会在关系型数据库环节失守,⽽这时候 Web 模块本身还远远未达到瓶颈。不管我们的服务瓶颈在哪⾥,最终要做的事情都是⼀样的,那就是流量限制。

常⻅的流量限制⼿段

流量限制的⼿段有很多,最常⻅的:漏桶、令牌桶两种:

  • 漏桶是指我们有⼀个⼀直装满了⽔的桶,每过固定的⼀段时间即向外漏⼀滴⽔。如果你接到了这滴⽔,那么你就可以继续服务请求,如果没有接到,那么就需要等待下⼀滴⽔。
  • 令牌桶则是指匀速向桶中添加令牌,服务请求时需要从桶中获取令牌,令牌的数⽬可以按照需要消耗的资源进⾏相应的调整。如果没有令牌,可以选择等待,或者放弃。

这两种⽅法看起来很像,不过还是有区别的。漏桶流出的速率固定,⽽令牌桶只要在桶中有令牌,那就可以拿。也就是说令牌桶是允许⼀定程度的并发的,⽐如同⼀个时刻,有 100 个⽤户请求,只要令牌桶中有 100 个令牌,那么这 100 个请求全都会放过去。令牌桶在桶中没有令牌的情况下也会退化为漏桶模型。

令牌桶
图:令牌桶

本文标题:Go语言Ratelimit服务流量限制

本文地址:https://www.hosteonscn.com/6803.html

评论

0条评论

发表评论

邮箱地址不会被公开。 必填项已用*标注