B站高可用架构实践

 欢迎关注方志朋的博客,回复”666“获面试宝典

【导读】本文整理了 B 站在云+社区沙龙分享的高可用架构,一起来学习小破站的稳定性实践吧!

流量洪峰下要做好高服务质量的架构是一件具备挑战的事情,从Google SRE的系统方法论以及实际业务的应对过程中出发,分享一些体系化的可用性设计。对我们了解系统的全貌上下游的联防有更进一步的了解。

负载均衡

474999553d3c1942d0af2ffcf481b195.png

BFE 就是指边缘节点,BFE 选择下游 IDC 的逻辑权衡:

  • 离 BFE 节点比较近的

  • 基于带宽的调度策略

  • 某个 IDC 的流量已经过载,选择另外一个 IDC

58aaadcd586111d2811484936daad96a.png

当流量走到某个 IDC 时,这个流量应该如何进行负载均衡?

2947ee336df1ca03957a8e58cb887d7b.png

问题:RPC 定时发送的 ping-pong,也即 healthcheck,占用资源也非常多。服务 A 需要与账号服务维持长连接发送 ping-pong,服务 B 也需要维持长连接发送 ping-pong。这个服务越底层,一般依赖和引用这个服务的资源就越多,一旦有任何抖动,那么产生的这个故障面是很大的。那么应该如何解决?

3c099af83a3fd88300e1417b6ca25de1.png

解决:以前是一个 client 跟所有的 backend 建立连接,做负载均衡。现在引入一个新的算法,子集选择算法,一个 client 跟一小部分的 backend 建立连接。图片中示例的算法,是从《Site Reliability Engineering》这本书里看的。

如何规避单集群抖动带来的问题?多集群。

9052a18b5de569cc483715858e38b443.png

如上述图片所示,如果采用的是 JSQ 负载均衡算法,那么对于 LBA 它一定是选择 Server Y 这个节点。但如果站在全局的视角来看,就肯定不会选择 Server Y 了,因此这个算法缺乏一个全局的视角。

如果微服务采用的是 Java 语言开发,当它处于 GC 或者 FullGC 的时候,这个时候发一个请求过去,那么它的 latency 肯定会变得非常高,可能会产生过载。

新启动的节点,JVM 会做 JIT,每次新启动都会抖动一波,那么就需要考虑如何对这个节点做预热?

0f7efa82036dab88c4de7ff6f62efa0e.png

如上图所示,采用 “the choice-of-2” 算法后,各个机器的 CPU 负载趋向于收敛,即各个机器的 CPU 负载都差不多。Client 如何拿到后台的 Backend 的各项负载?是采用 Middleware 从 Rpc 的 Response 里面获取的,有很多 RPC 也支持获取元数据信息等。

还有就是 JVM 在启动的时候做 JIT,以前的预热做法:手动触发预热代码,然后再引入流量,再进行服务发现注册等,不是非常通用。通过改进负载均衡算法,引入惩罚值的方式,慢慢放入流量进行预热。

限流

bc95a5accf98d6fa87c289a872f16cea.png

用 QPS 限制的陷阱:

  • 不同的参数,请求的数据量是不同的,对一个进程的一个吞吐是有影响的。

  • 业务是经常迭代的,配一个静态的阈值,这个非常困难。能否按照每一个服务用多少个 CPU 来做限流?

每一个 API 都是有重要性的:非常重要、次重要,这样配置限流、做过载保护的时候,可以使用不同的阈值。

每个服务都要配一个限流,是非常烦人的,需要压测,是不是可以自适应去限流

b0fbe828892f5b6b320faadc9cab6bac.png

每个 Client 如何知道自己这一次需要申请多少 Quota ?基于历史数据窗口的 QPS。

节点与节点之间是有差异的,分配算法不够好,会导致某些节点产生饥饿。那么可以采用最大最小公平算法,尽可能地比较公平地去分配资源,来解决这个问题。

92f48b9502eb1cceb5c9854d6d08a53b.png

当量再大一点的时候,如果 Backend 一直忙着拒绝请求,比如发送 503,那么它还是会挂掉。这种情况就要考虑从 Client 去截流。此处,又提到了 Google 《Site Reliability Engineering》这本书里面的一个算法,即 Client 是按照一定概率去截流。那么这个概率怎么计算?一个是总请求量:requests,一个是成功的请求量:accepts。如果服务报错率比较高,意味着 accepts 不怎么增长,requests 一直增长,最终这个公式求极限,它会等于 1,所以它的丢弃概率是非常高的。基于这么一个简单的公式,不需要依赖什么 ZooKeeper,什么协调器之类的,就可以得到一个概率丢弃一些请求。它尽可能的在服务不挂掉的情况下,放更多的流量进去,而不是像 Netflix 一样全部拒掉。

40e1d8790dd72678a2fd63f492f9134d.png

连锁故障通常都是某一个节点过载了挂掉,流量又会去剩下的 n - 1 个节点,又扛不住,又挂掉,所以最终一个一个挨着雪崩。所以过载保护的目的是为了自保。

B 站参考了阿里的 Sentinel 框架、Netflix 的一些文章等,最终采用的是类似于 TCP BBR 探测的思路和算法。简单说:当 CPU 达到 80% 的时候,这个时候我们认为流量过载,如果此时吞吐量比如 100,用它作为阈值,瞬时值的请求比如是 110,那就可以丢掉 10 个流量。这样就可以实现一个限流算法。

f91df9cf0523b4e7e6307cb2fc938dbe.png

CPU 抖来抖去,使用 CPU 滑动均值(绿色线)可以跳动的没有这么厉害。这个 CPU 针对不同接口的优先级,例如低优先级 80% 触发,高优先级 90% 触发,可以定为一个阈值。

那么吞吐如何计算?利特尔法则。当前的 QPS * 延迟 = 吞吐,可以用过去的一个窗口作为指标。一旦丢弃流量,CPU 立马下来,算法抖动非常厉害。图二右侧黄色线表示抖动非常高,绿色线表示放行的流量也是抖动非常高,所以又加了冷却时间,比如持续几秒钟,再重新判断。

重试

094d3d8159f71dc3a85d75f580de726c.png

  • BFE: 动态 CDN

  • SLB: LVS + Nginx 实现,四七层负载均衡

  • BFF: 业务逻辑组装、编排

问题:每一层都重试,这一层 3 次,那一层 3 次,会指数级的放大。解决:只在失败这一层重试,如果重试之后失败,请返回一个全局约定好的错误码,比如说:过载,无需重试,发现这个错误码,通通放行,避免级联重试。

重试都应该无脑的重试三次吗?API 级别的重试需要考虑集群的过载情况。是不是可以约定一个重试比例呢?比如只允许 10% 的流量进行重试,Client 端做统计,当发现有 10% 都是重试,那么剩下的都拒绝掉。这样最多产生 1.1 倍的放大,重试 3 次,极端情况下,会产生 3 倍放大。还有在重试的时候,尽量引入随机、指数递增的一个重试周期,大家不要都重试 1 秒钟,有可能会堆砌一个重试的波峰

重试的统计图和记录 QPS 的图分开。问题诊断的时候,可以知道它是来自流量重试导致的问题放大。

c02bfd6b5409540276ca55c2dd9f6dc2.png

某个服务不可用的时候,用户总是会猛点,那么这个时候,需要去限制它的频次,一个短周期内不允许发重复请求。这种策略,有可能会根据不同的过载情况经常调这种策略,那么可以挂载到每一个 API 里面。

超时

b3fa266cea893f3f2fff6b3fd1467741.png

大部分的故障都是因为超时控制不合理导致的。

  • 某个高延迟服务可能会导致 Client 堆积,Client 线程会阻塞,上游流量不断进来,下游的消费速度跟不上上游的流入速度,进程会堆积越来越多请求,可能会 OOM。

  • 超时的策略本质是就是为了丢弃或者消耗掉请求。

  • 下游 2 秒返回,上游配置了 1 秒,上游超时已经返回给用户,下游还在执行,浪费资源。

某个服务需要在 1 秒返回,内部可能需要访问 Redis,需要访问 RPC,需要访问数据库,时间加起来就超过 1 秒,那么访问完每一层,应该计算供下一层使用的超时时间还剩多少可用。在 go 语言里,可能会使用 Context,每一个网络请求开始的阶段,都要根据配置文件配置的超时时间,和当前剩余多少,取一个最小值,最终整个超时时间不会超过 1 秒。

2236cbacfb43e08dc2d9c94f2a032f87.png

通过 RPC 的元数据传递,类似 HTTP 的 request header,带给其它服务。例如在图中,就是把 700ms 这个配额传递给 Service B。

下游服务作为服务提供者,在他的 RPC.IDL 文件中把自己的超时要配上,那么用 IDL 文件的时候,就知道是 200 ms,不用去问。

应对连锁故障

571c7a165e0e9d09700c525e0b3b09cf.png

优雅降级:一开始千人千面,后来只返回热门的

QA

  • Q: 请问负载均衡依据的 metric 是什么?

  • A: 服务端主要用 CPU,客户端用的是健康度,指连接的成功率,延迟也很重要,每个 Client 往不同的 Backend 发了多少个请求,四个指标归一,写一个线性方程,进行打分。


  • Q: BFE 到 SLB 走公网还是专线?

  • A: 既有公网,又有专线。


  • Q: Client 几千量级,每 10 秒 ping-pong 一下,会不会造成蛮高的 CPU?

  • A: 如果 Backend 很多的话,那么这个的确会造成。


  • Q: 多集群切换是否有阻塞的点?

  • A: 一个 Client 连接到各个集群,subset 算法,每个集群都有 Cache


  • Q: 负载均衡的探针是怎么做的?

  • A: 惩罚值,比如 5 秒,慢慢放流量


  • Q: Quota-Server 限流有开源实现吗?

  • A: 目前看到的都是针对单节点的。


  • Q: 客户端统计是否有点太多?

  • A: 可以做到 Sidecar、Service Mesh 里面


  • Q: 超时传递是不是太严格?

  • A: 有些情况下即便超时也要运行,可以通过 RPC Context 管控


  • Q: 每个 RPC 都获取 CPU 会不会很昂贵?

  • A: 后台开启线程定时计算 CPU 平滑均值


  • Q: 线上压测和测试环境压测 CPU 不一致

  • A: RPC 路由加影子库


  • Q: CC 攻击

  • A: 边缘节点或者核心机房都有防止 CC 攻击的一些手段,只要不是分布式搞你,都能找到流量特征进行管控

转自:

kunzhao.org/docs/cloud-plus-bbs/bilibili-high-availability/

 
 
热门内容:

69cef013a88fd7a7948d6cae5458dcba.png

 
 
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值