1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00
This commit is contained in:
Xargin 2018-12-21 15:24:49 +08:00
parent 2a57def713
commit c718dec526

View File

@ -1,12 +1,12 @@
# 5.6 Ratelimit 服务流量限制
计算机程序可依据其瓶颈分为 Disk IO-boundCPU-boundNetwork-bound,分布式场景下有时候也会外部系统而导致自身瓶颈。
计算机程序可依据其瓶颈分为磁盘IO瓶颈型CPU计算瓶颈型网络带宽瓶颈型,分布式场景下有时候也会外部系统而导致自身瓶颈。
Web 系统打交道最多的是网络,无论是接收,解析用户请求,访问存储,还是把响应数据返回给用户,都是要走网络的。在没有 epoll/kqueue 之类的系统提供的 IO 多路复用接口之前,多个核心的现代计算机最头痛的是 C10k 问题C10k 问题会导致计算机没有办法充分利用 CPU 来处理更多的用户连接,进而没有办法通过优化程序提升 CPU 利用率来处理更多的请求。
Web系统打交道最多的是网络无论是接收解析用户请求访问存储还是把响应数据返回给用户都是要走网络的。在没有`epoll/kqueue`之类的系统提供的IO多路复用接口之前多个核心的现代计算机最头痛的是C10k问题C10k问题会导致计算机没有办法充分利用CPU来处理更多的用户连接进而没有办法通过优化程序提升CPU利用率来处理更多的请求。
自从 Linux 实现了 epollFreeBSD 实现了 kqueue,这个问题基本解决了,我们可以借助内核提供的 API 轻松解决当年的 C10k 问题,也就是说如今如果你的程序主要是和网络打交道,那么瓶颈一定在用户程序而不在操作系统内核。
自从Linux实现了`epoll`FreeBSD实现了`kqueue`这个问题基本解决了我们可以借助内核提供的API轻松解决当年的C10k问题也就是说如今如果你的程序主要是和网络打交道那么瓶颈一定在用户程序而不在操作系统内核。
随着时代的发展,编程语言对这些系统调用又进一步进行了封装,如今做应用层开发,几乎不会在程序中看到 epoll 之类的字眼大多数时候我们就只要聚焦在业务逻辑上就好。Go 的 net 库针对不同平台封装了不同的 syscall APIhttp 库又是构建在 net 库之上,所以在 Go 我们可以借助标准库,很轻松地写出高性能的 http 服务,下面是一个简单的 `hello world` 服务的代码:
随着时代的发展,编程语言对这些系统调用又进一步进行了封装,如今做应用层开发,几乎不会在程序中看到`epoll`之类的字眼大多数时候我们就只要聚焦在业务逻辑上就好。Go 的 net 库针对不同平台封装了不同的syscall API`http`库又是构建在`net`库之上所以在Go语言中我们可以借助标准库很轻松地写出高性能的`http`服务,下面是一个简单的`hello world`服务的代码:
```go
package main
@ -87,13 +87,13 @@ Requests/sec: 45118.57
Transfer/sec: 5.51MB
```
多次测试的结果在 4w 左右的 QPS 浮动,响应时间最多也就是 40ms 左右对于一个Web程序来说这已经是很不错的成绩了我们只是照抄了别人的示例代码就完成了一个高性能的 `hello world` 服务器,是不是很有成就感?
多次测试的结果在4万左右的QPS浮动响应时间最多也就是40ms左右对于一个Web程序来说这已经是很不错的成绩了我们只是照抄了别人的示例代码就完成了一个高性能的`hello world`服务器,是不是很有成就感?
这还只是家用PC线上服务器大多都是24核心起32G内存+CPU基本都是Intel i7。所以同样的程序在服务器上运行会得到更好的结果。
这里的 `hello world` 服务没有任何业务逻辑。真实环境的程序要复杂得多,有些程序偏 Network-bound例如一些 cdn 服务、proxy 服务;有些程序偏 CPU/GPU bound例如登陆校验服务、图像处理服务有些程序偏 Disk IO-bound,例如专门的存储系统,数据库。不同的程序瓶颈会体现在不同的地方,这里提到的这些功能单一的服务相对来说还算容易分析。如果碰到业务逻辑复杂代码量巨大的模块,其瓶颈并不是三下五除二可以推测出来的,还是需要从压力测试中得到更为精确的结论。
这里的`hello world`服务没有任何业务逻辑。真实环境的程序要复杂得多有些程序偏Network-bound例如一些cdn服务、proxy服务有些程序偏CPU/GPU bound例如登陆校验服务、图像处理服务有些程序瓶颈偏磁盘,例如专门的存储系统,数据库。不同的程序瓶颈会体现在不同的地方,这里提到的这些功能单一的服务相对来说还算容易分析。如果碰到业务逻辑复杂代码量巨大的模块,其瓶颈并不是三下五除二可以推测出来的,还是需要从压力测试中得到更为精确的结论。
对于 IO/Network bound 类的程序,其表现是网卡/磁盘 IO 会先于 CPU 打满,这种情况即使优化 CPU 的使用也不能提高整个系统的吞吐量,只能提高磁盘的读写速度,增加内存大小,提升网卡的带宽来提升整体性能。而 CPU bound 类的程序,则是在存储和网卡未打满之前 CPU 占用率提前到达 100%CPU 忙于各种计算任务IO 设备相对则较闲。
对于IO/Network瓶颈类的程序其表现是网卡/磁盘IO会先于CPU打满这种情况即使优化CPU的使用也不能提高整个系统的吞吐量只能提高磁盘的读写速度增加内存大小提升网卡的带宽来提升整体性能。而CPU bound类的程序则是在存储和网卡未打满之前CPU占用率提前到达100%CPU忙于各种计算任务IO设备相对则较闲。
无论哪种类型的服务在资源使用到极限的时候都会导致请求堆积超时系统hang死最终伤害到终端用户。对于分布式的Web服务来说瓶颈还不一定总在系统内部也有可能在外部。非计算密集型的系统往往会在关系型数据库环节失守而这时候Web模块本身还远远未达到瓶颈。
@ -110,6 +110,8 @@ Transfer/sec: 5.51MB
![token bucket](../images/ch5-token-bucket.png)
*图 5-12 令牌桶*
实际应用中令牌桶应用较为广泛,开源界流行的限流器大多数都是基于令牌桶思想的。并且在此基础上进行了一定程度的扩充,比如`github.com/juju/ratelimit`提供了几种不同特色的令牌桶填充方式:
```go
@ -122,13 +124,13 @@ func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
```
和普通的 NewBucket 的区别是,每次向桶中放令牌时,是放 quantum 个令牌,而不是一个令牌。
和普通的`NewBucket()`的区别是,每次向桶中放令牌时,是放`quantum`个令牌,而不是一个令牌。
```go
func NewBucketWithRate(rate float64, capacity int64) *Bucket
```
这个就有点特殊了,会按照提供的比例,每秒钟填充令牌数。例如 capacity 是 100而 rate 是 0.1,那么每秒会填充 10 个令牌。
这个就有点特殊了,会按照提供的比例,每秒钟填充令牌数。例如`capacity`是100`rate`是0.1那么每秒会填充10个令牌。
从桶中获取令牌也提供了几个API
@ -259,7 +261,7 @@ cur = cur > cap ? cap : cur
## 5.6.3 服务瓶颈和 QoS
前面我们说了很多 CPU-bound、IO-bound 之类的概念,这种性能瓶颈从大多数公司都有的监控系统中可以比较快速地定位出来,如果一个系统遇到了性能问题,那监控图的反应一般都是最快的。
前面我们说了很多CPU瓶颈、IO瓶颈之类的概念,这种性能瓶颈从大多数公司都有的监控系统中可以比较快速地定位出来,如果一个系统遇到了性能问题,那监控图的反应一般都是最快的。
虽然性能指标很重要但对用户提供服务时还应考虑服务整体的QoS。QoS全称是Quality of Service顾名思义是服务质量。QoS包含有可用性、吞吐量、时延、时延变化和丢失等指标。一般来讲我们可以通过优化系统来提高Web服务的CPU利用率从而提高整个系统的吞吐量。但吞吐量提高的同时用户体验是有可能变差的。用户角度比较敏感的除了可用性之外还有时延。虽然你的系统吞吐量高但半天刷不开页面想必会造成大量的用户流失。所以在大公司的Web服务性能指标中除了平均响应时延之外还会把响应时间的95分位99分位也拿出来作为性能标准。平均响应在提高CPU利用率没受到太大影响时可能95分位、99分位的响应时间大幅度攀升了那么这时候就要考虑提高这些CPU利用率所付出的代价是否值得了。