diff --git a/SUMMARY.md b/SUMMARY.md index e2e1ac5..b8ccb5b 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -45,6 +45,7 @@ * [5.3. Middleware中间件](ch5-web/ch5-03-middleware.md) * [5.4. Validator请求校验](ch5-web/ch5-04-validator.md) * [5.5. Database和数据库打交道](ch5-web/ch5-05-database.md) + * [5.6. Ratelimit 服务流量限制](ch5-web/ch5-06-ratelimit.md) * [5.7. Layout大型web项目分层](ch5-web/ch5-07-layout-of-web-project.md) * [5.9. 灰度发布和 A/B test](ch5-web/ch5-09-gated-launch.md) * [5.11. Load-balance负载均衡](ch5-web/ch5-11-load-balance.md) diff --git a/ch3-asm/ch3-04-func.md b/ch3-asm/ch3-04-func.md index 389af30..9c52767 100644 --- a/ch3-asm/ch3-04-func.md +++ b/ch3-asm/ch3-04-func.md @@ -191,6 +191,8 @@ func Foo() { 在前文中我们已经学习过一些汇编实现的函数参数和返回值处理的规则。那么一个显然的问题是,汇编函数的参数是从哪里来的?答案同样明显,被调用函数的参数是有调用方准备的:调用方在栈上设置好空间和数据后调用函数,被调用方在返回前将返回值放如对应的位置,函数通过RET指令返回调用放函数之后,调用方从返回值对应的栈内存位置取出结果。Go语言函数的调用参数和返回值均是通过栈传输的,这样做的有点是函数调用栈比较清晰,缺点是函数调用有一定的性能损耗(Go编译器是通过函数内联来缓解这个问题的影响)。 +![](../images/ch3-func-call-frame-01.ditaa.png) + 为了便于演示,我们先用Go语言构造foo和bar两个函数,其中foo函数内部调用bar函数: ```go diff --git a/ch5-web/ch5-06-ratelimit.md b/ch5-web/ch5-06-ratelimit.md index c9b8100..2eb1cf1 100644 --- a/ch5-web/ch5-06-ratelimit.md +++ b/ch5-web/ch5-06-ratelimit.md @@ -108,7 +108,7 @@ Transfer/sec: 5.51MB 这两种方法看起来很像,不过还是有区别的。漏桶流出的速率固定,而令牌桶只要在桶中有令牌,那就可以拿。也就是说令牌桶是允许一定程度的并发的,比如同一个时刻,有 100 个用户请求,只要令牌桶中有 100 个令牌,那么这 100 个请求全都会放过去。令牌桶在桶中没有令牌的情况下也会退化为漏桶模型。 -TODO,这里需要画漏桶和令牌桶的图 +![leaky bucket](../images/ch6-06-leaky-bucket.jpg) 实际应用中令牌桶应用较为广泛,开源界流行的限流器大多数都是基于令牌桶思想的。并且在此基础上进行了一定程度的扩充,比如 `github.com/juju/ratelimit` 提供了几种不同特色的令牌桶填充方式: @@ -244,8 +244,21 @@ func TakeAvailable(block bool) bool{ 一些公司自己造的限流的轮子就是用上面这种方式来实现的,不过如果开源版 ratelimit 也如此的话,那我们也没什么可说的了。现实并不是这样的。 -TODO +我们来思考一下,令牌桶每隔一段固定的时间向桶中放令牌,如果我们记下上一次放令牌的时间为 t1,和当时的令牌数 k1,放令牌的时间间隔为 ti,每次向令牌桶中放 x 个令牌,令牌桶容量为 cap。现在如果有人来调用 `TakeAvailable` 来取 n 个令牌,我们将这个时刻记为 t2。在 t2 时刻,令牌桶中理论上应该有多少令牌呢?伪代码如下: -## 题外话: 分布式流量限制 +```go +cur = k1 + ((t2 - t1)/ti) * x +cur = cur > cap ? cap : cur +``` -TODO,在线系统应该保证 CPU 资源的余量,以确保 QoS,不应完全把资源用暴。 +我们用两个时间点的时间差,再结合其它的参数,理论上在取令牌之前就完全可以知道桶里有多少令牌了。那劳心费力地像本小节前面向 channel 里填充 token 的操作,理论上是没有必要的。只要在每次 `Take` 的时候,再对令牌桶中的 token 数进行简单计算,就可以得到正确的令牌数。是不是很像 `惰性求值` 的感觉? + +在得到正确的令牌数之后,再进行实际的 Take 操作就好,这个 Take 操作只需要对令牌数进行简单的减法即可,记得加锁以保证并发安全。`github.com/juju/ratelimit` 这个库就是这样做的。 + +## 服务瓶颈和 QoS + +前面我们说了很多 CPU-bound、IO-bound 之类的概念,这种性能瓶颈从大多数公司都有的监控系统中可以比较快速地定位出来,如果一个系统遇到了性能问题,那监控图的反应一般都是最快的。 + +虽然性能指标很重要,但对用户提供服务时还应考虑服务整体的 QoS。QoS 全称是 Quality of Service,顾名思义是服务质量。QoS 包含有可用性、吞吐量、时延、时延变化和丢失等指标。一般来讲我们可以通过优化系统,来提高 web 服务的 CPU 利用率,从而提高整个系统的吞吐量。但吞吐量提高的同时,用户体验是有可能变差的。用户角度比较敏感的除了可用性之外,还有时延。虽然你的系统吞吐量高,但半天刷不开页面,想必会造成大量的用户流失。所以在大公司的 web 服务性能指标中,除了平均响应时延之外,还会把响应时间的 95 分位,99 分位也拿出来作为性能标准。平均响应在提高 CPU 利用率没受到太大影响时,可能 95 分位、 99 分位的响应时间大幅度攀升了,那么这时候就要考虑提高这些 CPU 利用率所付出的代价是否值得了。 + +在线系统的机器一般都会保持 CPU 有一定的余裕。 diff --git a/images/ch3-func-call-frame-01.ditaa.png b/images/ch3-func-call-frame-01.ditaa.png new file mode 100644 index 0000000..14aa74c Binary files /dev/null and b/images/ch3-func-call-frame-01.ditaa.png differ diff --git a/images/ch3-func-call-frame-01.ditaa.txt b/images/ch3-func-call-frame-01.ditaa.txt new file mode 100644 index 0000000..e5e77f6 --- /dev/null +++ b/images/ch3-func-call-frame-01.ditaa.txt @@ -0,0 +1,51 @@ + function call frame + ++----------+<-=-g.stack.hi +| stack | +| cRED | func main() +| | main frame ++----------+<-=-----+----------+ +| main | | cGRE | +| cGRE | | local | func printsum(a, b int) +| | | | printsum frame +| | +----------+ +----------+ +| | | | : | +| | | +8(SP) |----=--->| b+8(FP) | +| call | +8(SP)| cPNK | arg b | cGRE |b+8(FP) +| printsum | +----------+ +----------+ +| | | | : | +| | | +0(SP) |----=--->| a+0(FP) | +| | +0(SP)| cPNK | arg a | cGRE |a+0(FP) ++----------+<-=-----+----------+<-=------+----------+ +| printsum | | | +| cYEL | |var c int | func sum(a, b int) int +| | | cYEL |c-8(SP) sum frame +| | +----------+ +----------+ +| call sum | | | : | +| | | sum.ret |<----=---|ret+24(FP)| +| | +16(SP)| cPNK | return | |ret+16(FP) +| | +----------+ +----------+ +| | | | : | +| | | sum.b |-----=-->| b+8(FP) | +| | +8(SP)| cPNK | arg b | |b+8(FP) +| | +----------+ +----------+ +| | | | : | +| | | sum.a |-----=-->| a+0(FP) | +| | +0(SP)| cPNK | arg a | |a+0(FP) ++----------+<-=--------------------------+----------+<-=------+----------+ +| func sum | | cBLU |t-8(SP) +| cBLU | | local | +| ret a+b | | | ++----------+<-=-----------------------------------------------+----------++0(SP) +| cAAA |+0(SP) +| unused | +| | +| | +| | +| | ++----------+<-=-g.stack.lo +| cRED | +|StackLimit| +|StackGuard| ++----------+ + diff --git a/images/ch6-06-leaky-bucket.jpg b/images/ch6-06-leaky-bucket.jpg new file mode 100644 index 0000000..6722e62 Binary files /dev/null and b/images/ch6-06-leaky-bucket.jpg differ