技术问答集录(五)(服务端全局限流和客户端Mock)
如何让开发无需编码实现服务端全局限流和客户端Mock两大功能?
限流
1.1 限流是在遇到流量高峰期、流量突增时,把流量速率限制在系统所能接受的合理范围内,防止系统被高流量击垮。
1.2常用限流算法
需要结合容量和压测来进行,请求超过或者接近系统设置的阀值,触发限流,采取其他手段进行降级,保证系统不被压垮,降级策略包括:延迟处理,拒绝服务,随机拒绝等
限流方案很多,每种方案都有自己的限流算法、适用的场景。整理了如下几种常用的限流算法。
1.2.1 固定窗口计数器(计数器法)
- 将时间划分Wie固定窗口大小,例如1S
- 在窗口时间段内,每来一个请求,对计数器加1
- 当计数器达到设定限制后,该窗口时间内之后的请求都被丢弃处理
- 该窗口结束后,计数器清零,从新的开始技术
固定时间内处理多少个请求数
在每个计时窗口内,每有1次请求,计数器加1;如果计数器超过了限制数量,则本窗口内所有的请求都被丢弃;当时间到达下1个计时窗口时,计数器被重置。比如1s 处理100个请求,超出则触发降级措施
优点:算法实现简单。
缺点:这个算法可能会让通过的请求量翻倍。比如,限制1秒内最多通过10个请求,在第一个计时窗口的后半秒通过10个请求,下一个窗口的前半秒通过10个请求,这样1秒内则通过了20个请求。
1.2.2 滑动窗口计数器
- 将时间分为细粒度区间,每个区间维持一个计数器,每进入一个请求则计数器加一。
- 多个区间组成一个时间窗口,每流逝一个区间时间后,则抛弃最老的一个区间,纳入新区间
- 若当前窗口的区间计数器总和超过设定的限制数量,则本窗口内的后续请求就被丢弃
(将1s处理100个请求,分为多个时间段,拆分为10个100ms,每个100ms处理10个请求,类似于队列,先进先出,第一个100ms,请求10个,时间到之后,则新建另外一个100ms,处理下个10个请求)
比如服务每秒最多处理10个请求,设置一个1秒钟的滑动窗口,窗口中有10个格子,每格为100毫秒,记录请求数;每100毫秒移动一次,抛弃最老的格子,增加一个新格,如果当前窗口内的请求总数超过了限制,则抛弃请求。
优点:避免了固定窗口计数器的请求翻倍。
缺点:时间划分的区间精度越高,算法所需的空间容量就越大。
1.2.3 漏桶
(白话就是,请求放入桶里面,然后从桶里面获取请求进行执行,当请求超出桶的大小就拒绝处理)
10个请求放入桶(放入队列,设置队列长度)3个线程处理(固定3个线程去处理,保证系统不会被压垮)
漏桶算法多使用队列实现,服务请求存储到队列中,如果外部请求超出当前阀值,则会容易积蓄,一直到溢出,系统并不关心溢出的流量。从出口处(从队列里面获取请求执行)限制请求速率,不存在计数器临界问题,请求曲线始终是平衡的,无法应对突发流量,服务提供方则按照固定的速率从队列中取出请求并执行,过多的请求则放在队列中排队或直接拒绝。相当于一个空桶+固定处理线程
Nginx接入层限流的ngx_http_limit_req_module模块,是基于漏桶算法实现。
优点:平滑了突发流量。
缺点:当短时间内有大量请求时,即便此时服务负载很低,每个请求也要在队列中等待一段时间才能被响应。无法应对突发流量
漏桶保证的是下游系统,固定的请求不会打入到下游系统,令牌桶可能桶里有100个令牌同时被100个请求获取到,那么可能导致下游系统突然暴增
Guava也提供了SmoothWarmingUp来实现这种需求,其可以认为是漏桶算,法,但是在某些特殊场景又不太一样
1.2.4 令牌桶
假设一个大小恒定的桶,这个桶的容量和设定的阀值有关,桶里放着很多令牌,通过一个固定的速率,往里边放入令牌,如果桶满了,就把令牌丢掉,最后桶中key保存的最大令牌数永远不超过桶的大小,当有请求进入是,就尝试从桶里取走一个令牌,如果桶里面是空的,那额这个请求就会被拒绝(白话翻译:设置一个有令牌的桶,请求从令牌桶里面获取令牌,然后请求处理,当令牌桶里面没有令牌的时候就拒绝请求,也可以同时有多个请求取走多个令牌,存放令牌的桶是设置有固定大小的,取令牌没有限制获取多少个)
生成的令牌以固定速率放入令牌桶中,如果令牌桶满了则多余的令牌会直接丢弃;当请求到达时,尝试从令牌桶中获取令牌,如果取到令牌则请求执行,如果桶空了,那么尝试取令牌的请求会被抛弃。
优点:能够限制请求的平均速率,还允许某种程度的突发流量;可以方便的改变请求速率,当需要提高速率时,则按需提高放入桶中令牌的速率。处理的速度不是固定的,咯同
缺点:容易导致误判。
Guava RateLimiter 提供的令牌桶算法可用于平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现
此外还有其他利用redis存储计数和计时实现分布式限流方式
无需开发的方式目前通过Nginx接入层限流比较符合
使用网关方案
Spring Cloud的Zuul网关可以实现这样的方案。其底层使用ribbon来实现请求的路由,并内置Hystrix,可选择性提供网关fallback逻辑。
Zuul中提供了过滤器定义,可以用来过滤代理请求。有前置过滤、路由后过滤、后置过滤、异常过滤等方式。 需要实现父类是ZuulFilter的过滤器。
Zuul网关组件也提供了限流保护。当请求并发达到阀值,自动触发限流保护,返回错误结果。只要提供error错误处理机制即可。
当请求通过zuul网关路由到服务,并等待服务返回响应,这个过程中zuul也有超时控制。zuul的底层使用的是Hystrix+ribbon来实现请求路由。结构如下:
参考 https://zhuanlan.zhihu.com/p/163074058
zuul中的Hystrix内部使用线程池隔离机制提供请求路由实现,其默认的超时时长为1000毫秒。ribbon底层默认超时时长为5000毫秒。如果Hystrix超时,直接返回超时异常。如果ribbon超时,同时Hystrix未超时,ribbon会自动进行服务集群轮询重试,直到Hystrix超时为止。如果Hystrix超时时长小于ribbon超时时长,ribbon不会进行服务集群轮询重试。
客户端mock
无需开发实现客户端mock功能,常见可以使用类似Eolinker,落兵台等mock平台编写api文档设置mock规则发布即可实现接口mock能力
同样用Zuul可以实现,用过滤器在路由后根据配置,可以将流量转发给模拟器,由模拟器返回响应结果