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 2019-01-01 20:38:56 +08:00
parent 581009122b
commit 092b2f1ebd

View File

@ -91,9 +91,9 @@ Transfer/sec: 5.51MB
这还只是家用PC线上服务器大多都是24核心起32G内存+CPU基本都是Intel i7。所以同样的程序在服务器上运行会得到更好的结果。 这还只是家用PC线上服务器大多都是24核心起32G内存+CPU基本都是Intel i7。所以同样的程序在服务器上运行会得到更好的结果。
这里的`hello world`服务没有任何业务逻辑。真实环境的程序要复杂得多,有些程序偏Network-bound例如一些cdn服务、proxy服务有些程序偏CPU/GPU bound,例如登陆校验服务、图像处理服务;有些程序瓶颈偏磁盘,例如专门的存储系统,数据库。不同的程序瓶颈会体现在不同的地方,这里提到的这些功能单一的服务相对来说还算容易分析。如果碰到业务逻辑复杂代码量巨大的模块,其瓶颈并不是三下五除二可以推测出来的,还是需要从压力测试中得到更为精确的结论。 这里的`hello world`服务没有任何业务逻辑。真实环境的程序要复杂得多,有些程序偏网络IO瓶颈例如一些CDN服务、Proxy服务有些程序偏CPU/GPU瓶颈,例如登陆校验服务、图像处理服务;有些程序瓶颈偏磁盘,例如专门的存储系统,数据库。不同的程序瓶颈会体现在不同的地方,这里提到的这些功能单一的服务相对来说还算容易分析。如果碰到业务逻辑复杂代码量巨大的模块,其瓶颈并不是三下五除二可以推测出来的,还是需要从压力测试中得到更为精确的结论。
对于IO/Network瓶颈类的程序其表现是网卡/磁盘IO会先于CPU打满这种情况即使优化CPU的使用也不能提高整个系统的吞吐量只能提高磁盘的读写速度增加内存大小提升网卡的带宽来提升整体性能。而CPU bound类的程序则是在存储和网卡未打满之前CPU占用率提前到达100%CPU忙于各种计算任务IO设备相对则较闲。 对于IO/Network瓶颈类的程序其表现是网卡/磁盘IO会先于CPU打满这种情况即使优化CPU的使用也不能提高整个系统的吞吐量只能提高磁盘的读写速度增加内存大小提升网卡的带宽来提升整体性能。而CPU瓶颈类的程序则是在存储和网卡未打满之前CPU占用率提前到达100%CPU忙于各种计算任务IO设备相对则较闲。
无论哪种类型的服务在资源使用到极限的时候都会导致请求堆积超时系统hang死最终伤害到终端用户。对于分布式的Web服务来说瓶颈还不一定总在系统内部也有可能在外部。非计算密集型的系统往往会在关系型数据库环节失守而这时候Web模块本身还远远未达到瓶颈。 无论哪种类型的服务在资源使用到极限的时候都会导致请求堆积超时系统hang死最终伤害到终端用户。对于分布式的Web服务来说瓶颈还不一定总在系统内部也有可能在外部。非计算密集型的系统往往会在关系型数据库环节失守而这时候Web模块本身还远远未达到瓶颈。
@ -118,7 +118,7 @@ Transfer/sec: 5.51MB
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
``` ```
默认的令牌桶fillInterval指每过多长时间向桶里放一个令牌capacity是桶的容量超过桶容量的部分会被直接丢弃。桶初始是满的。 默认的令牌桶,`fillInterval`指每过多长时间向桶里放一个令牌,`capacity`是桶的容量,超过桶容量的部分会被直接丢弃。桶初始是满的。
```go ```go
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
@ -144,7 +144,7 @@ func (tb *Bucket) Wait(count int64) {}
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {} func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {}
``` ```
名称和功能都比较直观,这里就不再赘述了。相比于开源界更为有名的google的Java工具库Guava中提供的ratelimiter这个库不支持令牌桶预热且无法修改初始的令牌容量所以可能个别极端情况下的需求无法满足。但在明白令牌桶的基本原理之后如果没办法满足需求相信你也可以很快对其进行修改并支持自己的业务场景。 名称和功能都比较直观,这里就不再赘述了。相比于开源界更为有名的Google的Java工具库Guava中提供的ratelimiter这个库不支持令牌桶预热且无法修改初始的令牌容量所以可能个别极端情况下的需求无法满足。但在明白令牌桶的基本原理之后如果没办法满足需求相信你也可以很快对其进行修改并支持自己的业务场景。
## 5.6.2 原理 ## 5.6.2 原理
@ -154,7 +154,7 @@ func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {}
var tokenBucket = make(chan struct{}, capacity) var tokenBucket = make(chan struct{}, capacity)
``` ```
每过一段时间向 tokenBucket 中添加 token如果 bucket 已经满了,那么直接放弃: 每过一段时间向`tokenBucket`中添加`token`,如果`bucket`已经满了,那么直接放弃:
```go ```go
fillToken := func() { fillToken := func() {
@ -248,7 +248,7 @@ func TakeAvailable(block bool) bool{
一些公司自己造的限流的轮子就是用上面这种方式来实现的,不过如果开源版 ratelimit 也如此的话,那我们也没什么可说的了。现实并不是这样的。 一些公司自己造的限流的轮子就是用上面这种方式来实现的,不过如果开源版 ratelimit 也如此的话,那我们也没什么可说的了。现实并不是这样的。
我们来思考一下,令牌桶每隔一段固定的时间向桶中放令牌,如果我们记下上一次放令牌的时间为 t1和当时的令牌数 k1放令牌的时间间隔为 ti每次向令牌桶中放 x 个令牌,令牌桶容量为 cap。现在如果有人来调用 `TakeAvailable` 来取 n 个令牌,我们将这个时刻记为 t2。在 t2 时刻,令牌桶中理论上应该有多少令牌呢?伪代码如下: 我们来思考一下,令牌桶每隔一段固定的时间向桶中放令牌,如果我们记下上一次放令牌的时间为 t1和当时的令牌数k1放令牌的时间间隔为ti每次向令牌桶中放x个令牌令牌桶容量为cap。现在如果有人来调用`TakeAvailable`来取n个令牌我们将这个时刻记为t2。在t2时刻令牌桶中理论上应该有多少令牌呢伪代码如下
```go ```go
cur = k1 + ((t2 - t1)/ti) * x cur = k1 + ((t2 - t1)/ti) * x
@ -257,7 +257,7 @@ cur = cur > cap ? cap : cur
我们用两个时间点的时间差再结合其它的参数理论上在取令牌之前就完全可以知道桶里有多少令牌了。那劳心费力地像本小节前面向channel里填充token的操作理论上是没有必要的。只要在每次`Take`的时候再对令牌桶中的token数进行简单计算就可以得到正确的令牌数。是不是很像`惰性求值`的感觉? 我们用两个时间点的时间差再结合其它的参数理论上在取令牌之前就完全可以知道桶里有多少令牌了。那劳心费力地像本小节前面向channel里填充token的操作理论上是没有必要的。只要在每次`Take`的时候再对令牌桶中的token数进行简单计算就可以得到正确的令牌数。是不是很像`惰性求值`的感觉?
在得到正确的令牌数之后再进行实际的Take操作就好这个Take操作只需要对令牌数进行简单的减法即可记得加锁以保证并发安全。`github.com/juju/ratelimit`这个库就是这样做的。 在得到正确的令牌数之后,再进行实际的`Take`操作就好,这个`Take`操作只需要对令牌数进行简单的减法即可,记得加锁以保证并发安全。`github.com/juju/ratelimit`这个库就是这样做的。
## 5.6.3 服务瓶颈和 QoS ## 5.6.3 服务瓶颈和 QoS