流量控制,当资源称为瓶颈的时候服务框架要对消费者做限流,启动流控保护机制
比较常用的有:
1)针对访问速率的静态流控
2)针对资源占用的动态流控
3)针对消费者并发连接数的连接流控
传统的流控采用的是安装预分配,在软件安装时,根据集群服务节点个数和静态流控阈值计算每个服务节点分摊的QPS阈值,系统运行时各个服务节点按照自己分配的阈值进行流控,对于超出流控阈值的请求则拒绝访问
拦截器会在服务调用前进行拦截计数,当计数器在指定周期T到达QPS上限时,启动流控,拒绝新的消息接入
缺点:
1)服务的异常和新节点的加入会使可用服务节点处于一个动态变化的过程中,预分配方案无法行通
2)服务节点宕机或者新服务加入节点发生变化的时候,静态分配的QPS需要实时动态调整,否则会导致流控不准
服务节点也会由于服务的动态上下线而处于频繁变化的状态,这种场景下静态分配显然没办法满足需求
云服务的资源也是动态分配的,静态分配阈值的方法没办法迁移到云上
动态流控:以服务注册中心以流控周期T为单位,动态推送每个节点分配的流控阈值QPS,当服务节点发生变更时,会触发服务注册中心重新计算每个节点的配额,然后进行推送,这样无论是新增还是减少服务节点,都可以在下一个流控周期内被识别和处理,实现动态变更
但这样存在的问题就是性能好的机器很快处理完请求,性能差的机器有未处理请求,导致系统总的配额未使用完,却发生了流控,可以在注册中心做配额计算时,根据节点性能的KPI数据做加权处理,处理能力差的少分配一些配额
另一种解决方式就是配额动态申请和返还:每个节点根据自己分配到的值和自身处理速率做出预测,若指标有剩余就返还给注册中心,若不够,就主动像注册中心申请,申请不到配额时就做出节流限制
这样做的限制是:
1)若流控周期T比较大,服务节点负反馈到注册中心,由注册中心统一计算之后再做配额均衡,误差较大
2)如果流控周期比较小,服务中心需要实时或者服务节点的性能KPI数据并计算负载情况,经过性能数据采集,上报汇总和计算之后,会有一定的时延,导致流控滞后产生误差
3)动态配额返还和重新申请会导致交互次数增加,同时也会存在时序误差,效果有限
4)扩展性查,当服务节点数越来越多的时候,注册中心的分配效率会急剧下降
可以采用动态配额申请:
1)初始部署的时候,根据节点数和静态流控QPS阈值,拿出一定比例的配额做初始分配,剩余的配额放在资源池
2)使用完配额的节点主动向注册中心申请配额,配额的申请:若流控周期为T,则将T分成更小的周期T/N N为经验值,默认为10 当前的节点总数M,则申请的配额为 (总的QPS-已分配的QPS配额)/M*T/N
3)总配额被申请完的时候,返回0个配额给申请的节点,就开始进行流控
注意一点:流控是为了保命,不是对流量进行限制,当系统负载压力过大的时候,系统进入过负载状态,可能是CPU、内存资源已过载,也可能是应用进程内的资源几乎耗尽,若继续处理全量业务可能会导致长时间的Full GC,消息严重堆压,或者进程宕机,最终将压力转移到集群其它节点,引起级联故障
流控也需要分级别,不同级别的流控,限制掉的流量值应该不同,比如1级流控,限制1/8的流量,二级流控限制1/4的流量
恢复的时候也要注意分级恢复
并发控制要针对服务提供者和消费者同时进行
服务提供者基于管道(pipeline)先获取流控阈值,然后从全局RPC上下文中获取当前并发执行数,若小于流控阈值,对当前计数器原子自增,否则抛出RPC流控异常,服务调用执行完毕,对计数器执行原子自减
客户端:获取流控阈值,检测当前并发数(从当前RPC上下文)若小于当前阈值,对计数器原子自增,反之,当前线程进入wait状态,wait超时时间为服务调用的超时时间,若其它线程完成,调用计数器自减,并发小于阈值,线程会被notify,退出wait