从 B 站崩溃的故障排查和恢复过程中学到什么?

2021 年 7 月 13 日晚上 22:52,B 站崩了。整个事件的罪魁祸首竟然只是,这么短短的几行代码;
  
local _gcd
_gcd = function (a, b)
    if b == 0 then
        return a
    end

    return _gcd(b, a % b)
end
回到 B 站崩溃那天,仅不到半个小时,这个消息就冲上了微博的热搜头条。从 B 站出来的用户甚至带崩了 A 站、豆瓣、知乎等多个网站。有网友调侃说,B 站服务器一蹦,紧张加班的除了 B 站的程序员,同时让 A 站\知乎、豆瓣、微博的程序员默默打开了电脑。B 站崩溃的背后,背后的根本原因到底是什么呢?
  
时隔一年以后,B 站对外公布了导致这个问题的底层原因,实际上这个问题并不复杂
   
了解 B 站的公网架构
但是为了让大家彻底搞懂这个问题,我们先来了解一下 B 站的公网架构。
1. CDN 是内容分发网络的一个简称,它提供了地域的就近访问功能,为用户获取服务器信息提供了加速的功能。
2. lvs 是一个四层负载均衡器,也就是基于 ip+端口的负载,为 OpenResty 提供高可用集群。
3. OpenResty,是一个基于 Nginx+Lua 的高性能 Web 平台,简单来说,就是我们可以直接使用 Lua 脚本来扩展 Nginx 的能力,从而构建动态的 Web 应用。
4. 最后,使用了多机房部署实现异地多活的架构保证高可用性。
   
当用户发起请求后,经过 CDN 分发到业务主机房,然后通过 LVS 四层负载路由到 OpenResty 服务器上,最后再转发到对应的应用服务器实例获取相关数据并返回。
  

  
故障解决过程分析
针对这样的一种架构,(如图)在 B 站崩溃的时候,运维人员采取了一些常规的措施去定位和解决问题。
1. 梳理整个请求链路,查看存在异常的服务器节点,SLB 运维人员发现七层负载服务器的 CPU 达到 100%,于是采取了重新加载以及冷重启 SLB 的方式都没有解决这个问题。
2.  接着发现多活机房在 CPU 使用率正常的情况下,SLB 存在大量超时请求,于是重启多活机房 SLB,恢复了部分业务的使用。
  

    
大家发现没有,遇到生产事故的时候,在不清楚具体问题的情况下,优先重启相关服务节点来达到故障止损是效率最高的手段,因此 B 站的运维人员通过重启 SLB 以及多活节点后。使得多活机房恢复正常后部分业务可以正常访问。但是业务主机房还没有恢复,这个时候,就需要慢慢去排查了。于是运维人员使用了 Linux 提供的 Perf 系统分析工具,定位到主机房 SLB 服务器的 CPU 热点集中在一个 Lua 函数上。于是,也采取了常规的解决思路- 版本回滚。一般一个问题的出现,在宏观层面没有太多变化的情况下,很有可能是因为最近发布了新版本导致的。从 B 站的问题复盘来看,回滚并没有解决问题,最后是通过重新构建新的 SLB 集群解决的。整个解决过程耗时 3 小时,对于互联网公司来说,这是一个非常大的故障。虽然问题解决了,但是这个问题的根源以及让谁来背这个锅,还没搞清楚,所以需要继续分析原因。经过一段时间的排查发现,CPU 跑满的原因是 OpenResty 里面的一个 lua 函数导致的 这个函数的作用是从注册中心同步服务注册地址以及该服务节点的访问权重保存到Nginx 的共享内存里面。然后使用 lua-resty-balancer 模块对服务地址进行动态路由。
    

   
其中,在进行目标服务器的动态选择的时候,用到了加权轮询算法,并使用了下面这个方法计算所有实例权重的最大公约数。这个方法本身没有问题,但是当节点的权重 weight=“0”的时候,_gcd 函数收到的入参 b 可能是字符串“0”。而 Lua 又是弱类型语言,允许传入字符串“0”,这个时候 if 条件字符串 0 不等于数字 0,会执行_gcd 递归调用,其中在执行 a%b 的时候,用字符串和数字取模,得到一个 NaN 的结果。于是再次执行的时候,就变成了_gcd(NaN,NaN)的递归调用导致死循环问题。
  
local _gcd
_gcd = function (a, b)
    if b == 0 then
        return a
    end

    return _gcd(b, a % b)
end
可能大家会有疑惑,为什么回滚代码没有解决这个问题。
官方的声明说了,权重这个功能是 2 个月之前上线的,也就是这个问题的潜在风险存在了 2 个月。
在某种发布模式下,应用实例的权重会短暂调整为 0,导致注册中心返回给 SLB 的权重是一个字符串的“0”。
   
这个问题之所以一直没暴露出来,是因为这种发布模式的使用频率极低。
一个影响生产环境 3 个小时,造成巨大影响和损失的生成事故,竟然是一个数据类型导致的。当得到这个答案的时候,大家可能会觉得难以接受,这就是千里之堤毁于蚁穴的真实写照啊。
   
在这个事故发生后,公司内部必然要做的几件事情
1.  出一份详细的事故报告,明确事故的责任人和事故的级别
2.  针对事故进行复盘。
3.  从技术层面、以及管理层面提出优化和改进的措施,避免后续再出现类似问题。
  
总的来说,越是偏向底层的开发,对于技术能力的要求以及工作的严谨性就越高。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸之间不太闲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值