今天这个话题,我相信对于做互联网业务的朋友不会陌生,特别是电商或者互金领域。一碰到做大规模促销活动,访问量会有十倍乃至百倍的增加,比如现在正在做的618。做技术的童鞋都知道,每次在做大型活动前,我们都会整备物资,查漏补缺,拉标语打口号,各种活动演练,期待打一个大胜仗。但是实际上,除了‘鹅猫狗饿’这样的大厂,其他小厂的软硬件都是容易吃紧的。那么真遇到流量吃不消了,就要考虑流量控制了,硬撑的结果,只会带来更大的系统性风险。
什么是流量控制?
流量控制就是监控当前系统的流量,如果超过预设的值或者当前系统的最大承受能力,则需要拒绝掉部分请求,以实现对系统的保护。最大承受能力的值来源于平时的压力或者性能测试的结果。
流量控制的策略
流量控制一般有两种策略方案:
1. 基于流量阈值的流控:流量阈值是每台主机的流量上限,流量超过该阈值主机将进入不稳定状态。阈值提前进行设定,如果主机当前流量超过阈值,则拒绝掉一部分流量,使得实际被处理流量始终低于阈值。
基于阈值的流控是最常见的一种方案,大多数的流控也是基于此,阈值一般通过压力测试确定。但是需要提前设置阈值,而且由于现在的系统多采用分布式的方案,需要有同步的机制,保证阈值能有效的同步。
2. 基于主机状态的流控:每个接受每个请求之前先判断当前主机状态,如果主机状况不佳,则拒绝当前请求。基于主机状态的流控的好处是省去了人为控制,不过难点在于需要制定一个稳定性的标准。而且还需要大量的实验来证明标准是否足够全面,否则不会触发流量控制。所以,如果对服务的状态没有足够的了解下,设置阈值的流控是最稳妥的办法。
流量控制的执行方案
按照服务端外部和内部的流量进行分类,有两种执行方案:
1. 外部HTTP访问流控
通过Nginx或者Tengine反向代理实现,基于各种策略进行流量控制。
例如,
(1). 限制每秒请求数
涉及模块:ngx_http_limit_req_module
通过漏桶原理来限制单位时间内的请求数,一旦单位时间内请求数超过限制,就会返回 503 错误。
范例:
http { limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
...
server {
...
location ~ \.php$ { limit_req zone=one burst=5 nodelay;
}
}
}复制代码
(2).白名单设置
http_limit_conn 和 http_limit_req 模块限制了单 IP 单位时间内的连接和请求数,但是如果 Nginx 前面有 lvs 或者 haproxy 之类的负载均衡或者反向代理,nginx 获取的都是来自负载均衡的连接或请求,这时不应该限制负载均衡的连接和请求,就需要 geo 和 map 模块设置白名单:
geo $whiteiplist {
default 1; 10.11.15.1610;
}
map $whiteiplist$limit { 1$binary_remote_addr; 0"";
}limit_req_zone $limit zone=one:10m rate=10r/s;limit_conn_zone $limit zone=addr:10m;
复制代码
2. 内部流量控制
对于内部服务的流量控制,可以考虑系统资源的使用情况,当系统资源成为瓶颈时,内部服务框架需要对消费者做限流,启动流控保护机制。对于内部流控检测的资源包括但不局限于以下系统:
CPU使用率。
内存使用率(对于Java,对应于JVM内存使用率)。
队列积压率。
当我们有明确的指标后,我们就需要制定对应的级别。不同级别拒掉的消息比例不同,这取决于资源的负载使用情况。例如当发生一级流控时,拒绝掉1/4的消息;发生二级流控时,拒绝掉1/2消息;发生三级流控时,所有的消息都被流控掉。
不同的级别有不同的流控阈值,系统上线后会提供默认的流控阈值,不同流控因子的流控阈值不同,业务上线之后通常会根据现场的实际情况做阈值调优,因此流控阈值需要支持在线修改和动态生效。
Dubbo服务框架提供服务调用入口的拦截点和切面接口,由业务实现自定义流控。也可以提供基础的流控框架,供业务实现流控条件判断、流控执行策略等,简化业务的定制工作量。
最后,讲个人的经验提示:当因为流控而拒绝请求时,务必在返回的数据中带上一个友好的提示信息,一方面能让终端用户感觉更友好,另外一方面,从技术端,我们也能明确用户之所以不能正常访问,是因为流控的原因,而不是客户端或者服务端的BUG,这是一个很大的坑。