平均月活跃用户达到1.3亿。在流量洪峰下实现B站的高可用架构是一个挑战。本文阐述了GoogleSRE的系统方法论和实际业务从响应过程出发,进行了一些系统的易用性设计。这将进一步帮助我们了解系统的整体情况和上下游联防。本文来自公众号云加社区(ID:QcloudCommunity)负载均衡负载均衡分为两个方向,一个是前端负载均衡,一个是数据中心内部负载均衡。在前端负载均衡方面,一般来说,用户流量访问级别主要是基于DNS,希望尽量减少用户请求的延迟。用户流量优化分布在多条网络链路、多数据中心、多服务器上,通过动态CDN方案实现最小延迟。以上图为例。用户流量首先会流入BFE的前端接入层。第一层的BFE实际上起到了路由器的作用,选择一个尽可能靠近接入节点的机房来加速用户请求。然后通过API网关转发到下游的服务层,可能是内部的一些微服务或者业务聚合层等,最终形成一个完整的流量格局。基于此,前端服务器的负载均衡主要考虑几个逻辑:尽量选择距离最近的节点,根据带宽策略调度选择API进入机房,根据可用服务容量均衡流量,最忙和最不忙节点消耗的CPU差异很小。但是如果负载均衡没做好,情况可能会像上图左边那样。这可能导致资源调度和编排困难,容器资源无法合理分配。因此,数据中心内部负载均衡主要考虑:均衡流量分配,可靠识别异常节点横向扩展,增加同构节点扩容,减少错误,提高可用性。之前我们发现内网业务在通过同构节点扩容时有CPU占用。过高的异常。通过排查,发现后面的RPC点对点通信之间的HealthCheck开销太大,导致了一些问题。另一方面,如果底层服务只有单套集群,发生抖动时故障面会比较大,需要引入多个集群来解决问题。通过实现Client到Backend的子集连接,我们可以将backend平均分配给clients,同时处理节点变化,持续平衡连接,避免大的变化。在多集群的情况下,需要考虑集群迁移的运维成本,集群之间的业务数据存在小的交集。回到繁忙或空闲时CPU占用率过高的问题,我们会发现这与背后的负载均衡算法有关。第一个问题:对于每一个QPS,其实每一次查询,每一次查询,每一次API请求,它们的成本都是不一样的。节点之间的差异非常大。即使做了均衡的流量分配,从负载的角度来看,其实也是不均衡的。第二个问题:物理机环境有差异。因为我们一般每年都会采购服务器,新采购的服务器通常CPU频率都比较高,所以服务器很难在本质上做到强同质化。基于此,参考JSQ(mostidletraining)负载均衡算法带来的问题,发现缺少的是服务器的全局视图,所以我们的目标需要综合考虑负载和可用性。我们参考《The power of two choices in randomized load balancing》的思想,使用choice-of-2算法,随机选择两个节点打分,选择一个比较好的节点:选择Backend:CPU,client:health,inflight,latency作为指标,以及使用简单的线性方程进行评分。对新启动的节点使用一个恒定的惩罚值(penalty),并使用probe方法来最小化负载量来预热。得分相对较低的节点可以避免进入“永久黑名单”而无法恢复,采用统计衰减的方法逐渐将节点指标恢复到初始状态(即默认值)。经过负载均衡算法的优化,我们取得了比较好的回报。限制电流以避免过载是负载平衡的一个重要目标。随着压力的增加,无论负载均衡策略多么有效,系统的某些部分总是会过载。我们优先考虑优雅降级、返回低质量结果和提供有损服务。在最坏的情况下,使用适当的节流来确保服务本身的稳定性。对于限流,我们认为主要集中在以下几点:QPS的限制带来了不同的请求成本和静态阈值的难以配置。根据API的重要性,按照优先级丢弃。为每个用户设置限制。当发生全局过载时,控制某些“异常”是很关键的。拒绝请求也是有代价的。每个服务都配置了限流带来的运维成本。在限流策略上,我们首先采用分布式限流。我们实现了一个quota-server来控制每个Client的Backend,即Backend需要向quota-server请求获取Quota。这样做的好处是可以减少请求服务器的频率,获取后直接在本地消费。在算法层面,采用最大和最小公平算法来解决大消费者带来的饥饿感。在客户端,当用户超过资源配额时,后端任务会迅速拒绝请求并返回“配额不足”的错误。有可能后端忙于不断发送拒绝请求,导致依赖资源过载和大量错误。在下游保护的两种情况下,我们选择直接在客户端进行流量,而不是将流量发送到网络层。我们在GoogleSRE中学到了一个有趣的公式,max(0,(requests-K*accepts)/(requests+1))。通过这个公式,我们可以让客户端直接发送请求,一旦超过限制,就会按照概率拦截流量。在过载保护方面,核心思想是在服务过载时丢弃一定量的流量,保证系统接近过载时的峰值流量,达到自我保护的目的。常见做法包括根据CPU和内存使用情况丢弃流量;使用队列进行管理;可控延迟算法:CoDel等。简单来说,当我们的CPU达到80%时,此时就可以认为接近过载了。如果此时的吞吐量达到100,请求的瞬时值为110,我可以丢掉这10个流量。在这种情况下,服务可以保护自己。基于这种思路,我们最终实现了一种过载保护算法。我们使用CPU的滑动平均值(CPU>800)作为启发式阈值,一旦触发,将进入过载保护阶段。算法为:(MaxPass*AvgRT)
