B站刚崩,唯品会又崩:亿级用户网站的架构硬伤与解决方案
文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
B站刚崩,唯品会又崩:亿级用户网站的架构硬伤与解决方案
说在前面
在40岁老架构师尼恩的数千读者群中,一直在指导大家简历和职业升级。前几天,指导了一个华为老伙伴的简历,小伙伴的优势在异地多活,但是在简历指导的过程中,尼恩发现: 异地多活的概念、异地多活的架构、非常重要,但是小伙伴却对整个异地多活的体系,不是太清晰。
而且,异地多活的架构非常重要,3月份出了两个大的线上事故,B站刚崩,唯品会又崩了。
在这里,尼恩给自己的 Future Super Architect Community (未来 超级 架构师 社区) 的小伙伴, 梳理一份顶级的解决方案。
主要的目标: 方便在架构指导的时候,作为参考资料。
当然,好知识不能独享, 这份方案,顺便通过尼恩的自媒体渠道公布给大家,为大家做架构提供参考资料。
也一并把这个方案作为系统高可用架构参考答案,收入咱们的《尼恩Java面试宝典》V76,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请到公号【技术自由圈】获取。
本文目录
接二连三的P0级事故(高可用事故)
-
3月5日 B站崩了
-
3月29日 唯品会又崩
P0级事故:3月5日 B站崩了
3月5日晚,尼恩的 Future Super Architect Community (未来 超级 架构师 社区)中,有小伙伴发现,从20:22分开始B站的服务器就处于状态,用户无法观看视频和刷新推荐内容。直至20:40左右才恢复正常,持续时间近二十分钟。
事故复盘
B站APP端,用户可以在有缓存的情况下打开首页,
在没有缓存的情况下,会得到的是报错提示,访问视频资源时无法正常加载页面。
Web端主站的用户点击“换一换”无法正常刷新,多尝试几次会得到提示“暂时没有新内容了”。
事故原因
次日凌晨 2 点,B 站发布公告称,昨晚,B 站的部分服务器机房发生故障,造成无法访问。
技术团队随即进行了问题排查和修复,现在服务已经陆续恢复正常。
影响范围
以B站6000万的日活、晚8点是传统的视频用户高活跃期来看,本次事故影响的用户量级超千万。
以互联网企业常规的事故评判标准来看,应该属于P0级事故,
对于这种严重的事故, 主要技术负责人面临扣绩效甚至背锅走人的风险。
P0级事故:3月29日 唯品会又崩
6.6 号,尼恩的 Future Super Architect Community (未来超级架构师 社区)中,有小伙伴贴出来一份唯品会的处罚公告。
小伙的问题是:大佬们说说,如何避免这种情况(P0级事故,负责人下课)。
事故复盘
3月29日发生的突发事件,唯品会App崩了当天冲上热搜。
故障的现象:“加购”等功能或出现异常。
故障的时长: 12小时。
6月6日消息,唯品会发布了一份处理公告,3月29日 唯品会App崩了的事故,判定为P0级故障。
什么是P0级 事故?
事故等级主要针对生产环境,划分依据类似于bug等级。
P0级 属于最高级别事故。 比如崩溃,页面无法访问,主流程不通,主功能未实现,或者在影响面上影响很大(即使bug本身不严重)。
P1级 属于高级别事故。 一般属于主功能上的分支,支线流程,核心次功能等
P2级 属于中级别事故。主要根据企业实际情况划分。
P3级 属于低级别事故。 主要根据企业实际情况划分。
PN级 ...级别越高。
P0属于最高级别事故,比如崩溃、页面无法访问、主流程不通、主功能未实现,或在影响面上影响很大(即使Bug本身不严重)。
官方在公告中称,此次南沙机房重大故障影响时间持续12个小时,导致公司业绩损失超亿元,影响客户达800多万。
唯品会表示,决定对此次事件严肃处理,对应部门的直接管理者承担此次事故责任。
关键是,主要技术负责人面临扣绩效甚至背锅走人:基础平台部负责人予以免职做相应处理。
架构师们,锅能不背么?
尼恩的 Future Super Architect Community (未来 超级 架构师) 社区中,有大量的架构师,很多都是资深的40岁老架构师。
所以,尼恩的视角,不是关注 B站、唯品会损失了多少亿。
而是关注的是咱们架构师的职业生涯, 简单来说,就是一句话:
架构师们,锅能不背么?
这里,先把责任明确一下。
基础平台部负责人 虽然是 管理岗,本身也有一定的架构职责,所以,从一定意义上说,也是架构师。
当然,有的小伙伴会说, 架构师就是技术军师、技术师爷, 负责出方案,出主意的。基础平台部负责人 不是架构师。
如果一定要这么说,也行。
既然老帅都丢了,军师会有好受的吗? 同样要背锅走人。
所以,还是同一个问题:架构师们,当如何拜托背锅走人的宿命?
什么是优秀的架构
如何不背锅呢?先得回到问题的本身。
尼恩为咱们的 Future Super Architect Community (未来 超级 架构师) 社区梳理过一个三高架构知识宇宙,其中有一张价值十万的架构师知识图谱。
在尼恩的那个三高架构知识宇宙知识图谱中,有一个超级大的图,给大家揭示了一个好的软件架构,应该遵循以下 3 个原则:
- 高性能
- 高并发
- 高可用
该图谱的url链接地址,在尼恩很多圣经电子书 PDF的最前面,在这里就不贴了
具体请参考 尼恩的《Java高并发核心编程 卷1 》《Java高并发核心编程 卷2 》《Java高并发核心编程 卷3 》 的PDF 最前面。
三高架构的三原则
原则1:高性能意味着系统拥有更大流量的处理能力,更低的响应延迟。
例如 1 秒可处理 10W 并发请求,接口响应时间 5 ms 等等。
原则2:高并发表示系统在迭代新功能时,能以最小的代价去扩展,系统遇到流量压力时,可以在不改动代码的前提下,去扩容系统。
原则3:高可用通常用 2 个指标来衡量:
- 平均故障间隔 MTBF(Mean Time Between Failure):表示两次故障的间隔时间,也就是系统「正常运行」的平均时间,这个时间越长,说明系统稳定性越高
- 故障恢复时间 MTTR(Mean Time To Repair):表示系统发生故障后「恢复的时间」,这个值越小,故障对用户的影响越小
可用性与这两者的关系:
可用性(Availability)= MTBF / (MTBF + MTTR) * 100%
这个公式得出的结果是一个比例,通常我们会用「N 个 9」来描述一个系统的可用性。
系统可用性 | 年故障时间 | 日故障时间 |
---|---|---|
90% (1个9) | 36.5天 | 2.4小时 |
99% (2个9) | 3.65天 | 14分钟 |
99.9% (3个9) | 8小时 | 86秒 |
99.99%(4个9) | 52分钟 | 8.6秒 |
99.999%(5个9) | 5分钟 | 0.86秒 |
99.9999%(6个9) | 32秒 | 86毫秒 |
从这张图,大家最好是能耳熟能详。
从图中,可以看到,要想达到 4 个 9 以上的可用性,一年的不可以时间为 52分钟,平均每天故障时间必须控制在 10 秒以内。
什么是优秀的架构?
原则1告诉大家:要用最少的资源,得到最大的受益。
原则2告诉大家:要能高扩展、自伸缩,能够承担高吞吐、高并发,能够自动扩容缩容
原则3告诉大家: 一年的可用性要达到至少 4个9,不可用时间不能超过 52分钟。
从这个角度来说, 前面的 B站事故、唯品会事故,都说明了一个问题:
B站、唯品会,都没有实现原则3的高可用: 高可用都没有达到4个9,更不用说5个9。
从这个维度来说: B站、唯品会这么多人、这么大的技术团队,并没有做到架构的优秀。
他们的架构团队,到了应该好好反思的时刻了。
是时候好好的反思了。
两个P0级故障的根因分析
尼恩为咱们的 Future Super Architect Community (未来 超级 架构师) 社区的小伙伴,指导架构转型和升级的时候,首先强调的是:根因分析
一般来讲,无论B站还是唯品会,单机房内部的链路,一定是高可用的。
为啥?那么多架构师,如果单机房内部的高可用架构都实现不了,那就白混了。
如果有读者确实不知道:怎么做单机房内部的高可用架构。
这个简单, 看看40岁老尼恩的 价值10W架构师知识图谱,翻翻尼恩的博客,就大致知道了。
单机房内部高可用,有架构师保障。
而机房的高可用,由机房厂商保障: 建设一个机房的要求其实是很高的,地理位置、温湿度控制、备用电源等等,机房厂商会在各方面做好防护。
关键是,怎么确保 地域维度的不出现基础设施问题呢?
比如:
-
2015 年 5 月 27 日,杭州市某地光纤被挖断,近 3 亿用户长达 5 小时无法访问支付宝
-
2021 年 7 月 13 日,B 站部分服务器机房发生故障,造成整站持续 3 个小时无法访问
-
2021 年 10 月 9 日,富途证券服务器机房发生电力闪断故障,造成用户 2 个小时无法登陆、交易
-
2023 年 3月29日,唯品会遭遇了一场灾难性的机房故障, 南沙IDC冷冻系统故障导致机房设备温度快速升高宕机,造成线上商城停止服务。事故影响时间持续12个小时,导致唯品会业绩损失超亿元,影响客户达800多万
-
.....都是P0级事故
可见,即使机房级别的防护已经做得足够好,但只要 地域维度的基础设施问题(网络问题、电力问题、地震问题、水灾问题),咱们的系统,也就不可用了。
如何解决地域维度的基础设施问题 呢?
对比起, 这个神也搞不定。
咱们作为应用架构师,在这块只能规避,只能规避,只能规避。
如何规避呢? 核心的措施就是 :异地多活。
什么是异地多活
异地多活的概念很多,像什么同城双活、两地三中心、三地五中心等等概念。
要想理解异地多活,需要从架构设计的3高原则说起。
常见的多活方案
4 个 9 高可用的核心方案就是异地多活
异地多活指分布在异地的多个站点同时对外提供服务的业务场景。
异地多活是高可用架构设计的一种,与传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。
常见的多活方案有同城双活、两地三中心、三地五中心等多种技术方案,
方案1:同城双活
同城双活是在同城或相近区域内建立两个机房。
同城双机房距离比较近,通信线路质量较好,比较容易实现数据的同步复制 ,保证高度的数据完整性和数据零丢失。
同城两个机房各承担一部分流量,一般入口流量完全随机,内部RPC调用尽量通过就近路由闭环在同机房,相当于两个机房镜像部署了两个独立集群,数据仍然是单点写到主机房数据库,然后实时同步到另外一个机房。
下图展示了同城双活简单部署架构,当然一般真实部署和考虑问题要远远比下图复杂。
服务调用基本在同机房内完成闭环,数据仍然是单点写到主机房数据储存,然后实时同步复制到同城备份机房。
当机房A出现问题时候运维人员只需要通过GSLB或者其他方案手动更改路由方式将流量路由到B机房。
同城双活可有效用于防范火灾、建筑物破坏、供电故障、计算机系统及人为破坏引起的机房灾难。
同城双活中的核心组件GSLB的原理,可以参见 尼恩的高并三部曲 之三《Java高并发核心编程 卷3 加强版》PDF。
同城双活关键:
(1)如何双机房切流
(2) 如何保证数据一致性
如何双机房切流
那怎么让 B 机房也接入流量呢?
最为简单的措施,就是进行 DNS切流。把 B 机房的接入层 IP 地址,加入到 DNS 中,这样,B 机房从上层就可以有流量进来了。
如何保证数据一致性
业务应用在操作数据库时,需要区分「读写分离」,假设A主B从,
-
「读」流量,可以读任意机房的存储,
-
「写」流量,只允许写 A 机房,因为主库在 A 机房。
-
然后进行A -B 机房的数据同步
这种架构,涉及用的所有存储,例如项目中用到了 MySQL、Redis、MongoDB 等等,
操作这些数据库,都需要区分读写请求,所以这块需要一定的业务「改造」成本。
最好的方式: 是通过 proxy 中间组件,完成 统一的 读写改造。
为啥不是立即做到异城多活?
上面的方案,仅仅是同城多活,不是异城多活。
同城多活可以解决 机房级别的不可抗拒灾难,但是 地域级别的不可抗拒灾难,就没有办法搞定。
同城多活的机房,放到两个城市,不就变成异城多活了吗?
没有那么简单,来看看问题吧。
一般来说,多活机房的网络是通过「跨城专线」连通的。
如果两个机房距离较远,受到物理距离的限制,现在,两地之间的网络延迟就变成了「不可忽视」的因素了。
比如:北京到上海的距离大约 1300 公里,即使架设一条高速的「网络专线」,光纤以光速传输,一个来回也需要近 10ms 的延迟。
况且,网络线路之间还会经历各种路由器、交换机等网络设备,实际延迟可能会达到 30ms ~ 100ms,如果网络发生抖动,延迟甚至会达到 1 秒。
此时两个机房都接入流量,那上海机房的请求,可能要去读写北京机房的存储,这里存在一个很大的问题:网络延迟、用户体验差、数据存在丢失风险。
也就是说,如果是异城多活,距离太远,网络延迟太大。
这个时候, 如果A机房挂了,而数据还没有完成同步,就会出现1秒的数据丢失。
再来个用户体验差的案例: 一个客户端请求打到上海机房,上海机房要去读写北京机房的存储,一次跨机房访问延迟就达到了 30ms,这大致是机房内网网络(0.5 ms)访问速度的 60 倍(30ms / 0.5ms),一次请求慢 60 倍,来回往返就要慢 100 倍以上。
而我们在 App 打开一个页面,可能会访问后端几十个 API,每次都跨机房访问,整个页面的响应延迟有可能就达到了秒级,这个性能简直惨不忍睹,难以接受。
所以:
同城多活的机房,放到两个城市,不就变成异城多活了吗? 这种想法,太肤浅了。
方案2:两地三中心
所谓两地三中心是指 同城双中心 + 异地灾备中心。
异地灾备中心是指在异地的城市建立一个备份的灾备中心,用于双中心的数据备份,数据和服务平时都是冷的,
当双中心所在城市或者地区出现异常而都无法对外提供服务的时候,异地灾备中心可以用备份数据进行业务的恢复。
两地三中心方案特点
优势
- 服务同城双活,数据同城灾备,同城不丢失数据情况下跨机房级别容灾。
- 架构方案较为简单,核心是解决底层数据双活,由于双机房距离近,通信质量好,底层储存例如mysql可以采用同步复制,有效保证双机房数据一致性。
- 灾备中心能防范同城双中心同时出现故障时候利用备份数据进行业务的恢复。
劣势
- 数据库写数据存在跨机房调用,在复杂业务以及链路下频繁跨机房调用增加响应时间,影响系统性能和用户体验。
- 服务规模足够大(例如单体应用超过万台机器),所有机器链接一个主数据库实例会引起连接不足问题。
- 出问题不敢轻易将流量切往异地数据备份中心,异地的备份数据中心是冷的,平时没有流量进入,因此出问题需要较长时间对异地灾备机房进行验证。
同城双活和两地三中心建设方案建设复杂度都不高,两地三中心相比同城双活有效解决了异地数据灾备问题,但是依然不能解决同城双活存在的多处缺点,想要解决这两种架构存在的弊端就要引入更复杂的解决方案去解决这些问题。
方案3:单元化+异地多活
阿里在实施这种方案时,给它起了个名字,叫做「单元化」。
单元化+异地多活结合的策略,是真正的异地多活策略,核心点有两个:
- 用户单元化
- 数据全局化
什么是用户单元化
同一个用户只会落在同一个机房内。之后的所有业务操作,都在这一个机房内完成,从根源上避免「跨机房」。
正常情况下,但用户的请求处理不会在两个机房「漂移」。
用户单元化的核心措施:要在最上层就把用户「区分」开,部分用户请求固定打到北京机房,其它用户请求固定打到上海 机房,进入某个机房的用户请求,之后的所有业务操作,都在这一个机房内完成,从根源上避免「跨机房」。
安全起见,每个机房在写存储时,还需要有一套机制,能够检测「数据归属」,应用层操作存储时,需要通过中间件来做「兜底」,避免不该写本机房的情况发生。
用户单元化之后,就可以进行用户分片。
分片的核心思路在于,让同一个用户的相关请求,只在一个机房内完成所有业务「闭环」,不再出现「跨机房」访问。
什么是数据全局化
多个机房,数据都是全量数据。
当然,也可以部分全量,部分进行分区域存储。
架构的本质,都不是一刀切。
如果一刀切,就违背了尼恩的 Future Super Architect Community (未来 超级 架构师) 社区的架构精神。
怎么做到数据全局化呢?
多个机房在接收「读写」流量(做好分片的请求),底层存储保持「双向」同步,两个机房都拥有全量数据
当任意机房故障时,另一个机房就可以「接管」全部流量,实现快速切换
用户单元化场景的流量路由
单元化+异地多活策略中,在接入层之上,再部署一个「路由层」(通常部署在云服务器上)
流量路由层的职责,就是把用户「分流」到不同的机房内。
多机房流量路由的规则
但这个路由规则,具体怎么定呢?
大致的路由规则有:
- 按业务类型分片
- 直接哈希分片
- 按地理位置分片
1、按业务类型分片
假设有 4 个应用,北京和上海机房都部署这些应用。
但应用 1、2 只在北京机房接入流量,在上海机房只是热备。
应用 3、4 只在上海机房接入流量,在北京机房是热备。
这样一来,应用 1、2 的所有业务请求,只读写北京机房存储,应用 3、4 的所有请求,只会读写上海机房存储。
这里按业务类型在不同机房接入流量,还需要考虑多个应用之间的依赖关系,要尽可能的把完成「相关」业务的应用部署在同一个机房,避免跨机房调用。
2、直接哈希分片
比如路由层会根据用户 ID 计算「哈希」取模,然后从路由表中找到对应的机房,之后把请求转发到指定机房内。
举例:一共 200 个用户,根据用户 ID 计算哈希值,然后根据路由规则,
用户 1 - 100 路由到北京机房,
用户101 - 200 用户路由到上海机房,
这样,就避免了同一个用户修改同一条数据的情况发生。
3、按地理位置分片
按地理位置分片方案,非常适合与地理位置密切相关的业务,例如打车、外卖服务就非常适合这种方案。
外卖肯定是「就近」点餐,整个业务范围相关的有商家、用户、骑手,它们都是在相同的地理位置内的。
针对这种特征,就可以在最上层,按用户的「地理位置」来做分片,分散到不同的机房。
举例:北京、河北地区的用户点餐,请求只会打到北京机房,而上海、浙江地区的用户,请求则只会打到上海机房。这样的分片规则,也能避免数据冲突。
总之,
至此,我们才算实现了真正的「异地双活」!
到这里你可以看出,完成这样一套架构,需要投入的成本是巨大的。
路由规则、路由转发、数据同步中间件、数据校验兜底策略,不仅需要开发强大的中间件,同时还要业务配合改造(业务边界划分、依赖拆分)等一些列工作,没有足够的人力物力,这套架构很难实施。
异地多活3大挑战
1、数据同步延迟挑战
(1)应用要走向异地,首先要面对的便是物理距离带来的延时。
如果某个应用请求需要在异地多个单元对同一行记录进行修改,为满足异地单元间数据库数据的一致性和完整性,需要付出高昂的时间成本。
(2)解决异地高延时即要做到单元内数据读写封闭,不能出现不同单元对同一行数据进行修改,所以我们需要找到一个维度去划分单元。
(3)某个单元内访问其他单元数据需要能正确路由到对应的单元,例如A用户给B用户转账,A用户和B用户数据不在一个单元内,对B用户的操作能路由到相应的单元。
(4)面临的数据同步挑战,对于单元封闭的数据需全部同步到对应单元,对于读写分离类型的,我们要把中心的数据同步到单元。
2、单元化解耦挑战
所谓单元(下面我们用RZone代替),是指一个能完成所有业务操作的自包含集合,在这个集合中包含了所有业务所需的所有服务,以及分配给这个单元的数据。
单元化架构就是把单元作为系统部署的基本单位,在全站所有机房中部署数个单元,每个机房里的单元数目不定,任意一个单元都部署了系统所需的所有的应用。
单元化架构下,服务仍然是分层的,不同的是每一层中的任意一个节点都属于且仅属于某一个单元,上层调用下层时,仅会选择本单元内的节点。
选择什么维度来进行流量切分,要从业务本身入手去分析。
例如电商业务和金融的业务,最重要的流程即下单、支付、交易流程,通过对用户id进行数据切分拆分是最好的选择,买家的相关操作都会在买家所在的本单元内完成。
对于商家相关操作则无法进行单元化,需要按照下面介绍的非单元化模式去部署。
当然用户操作业务并非完全能避免跨单元甚至是跨机房调用,例如两个买家A和B转账业务,A和B所属数据单元不一致的时候,对B进行操作就需要跨单元去完成,后面我们会介绍跨单元调用服务路由问题。
3、流量的路由挑战
- 流量调度,系统部署过去后流量怎么跟着怎么过去。
- 流量自闭环。由于距离的原因,跨地域的物理延时是没法避免的,流量过去之后怎么保证所有的操作都在本地完成,如果做不到那怎么将这种延时影响降到最低。
- 容灾切流。当某个机房出现故障时,如何快速把流量无损地切至其他机房。这里并不是说简单把流量切过去就完事,由于数据在多区域同步,流量切过去之后能否保证数据的一致性?
异地多活成功案例:得物APP的异地多活改造
看到前面那么多P0级异地多活案例,咱们看看成功的案例吧:
另外,此文的异地多活架构,和尼恩其他的架构文章一起,
组成一个架构知识系统,帮助大家实现你的 架构自由:
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
架构和高级开发不一样: 架构的问题是open的、开发式的、没有标准答案的。
架构之路,注定是充满了坎坷。
在做架构过程中,或者在转型过程中,如果遇到复杂的场景,确实不知道怎么做架构方案,确实找不到有底的方案,怎么办? 可以以来找40岁老架构尼恩求助.
就在前几天,一个小伙伴遇到了一个 电商网站的黄金链路架构, 开始找不到思路,但是经过尼恩 10分钟语音指导,一下就豁然开朗。
so,大家如果遇到架构问题,甚至架构难题,可以找尼恩来交流,来求助。
技术自由的实现路径:
实现你的 响应式 自由:
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
实现你的 spring cloud 自由:
《Spring cloud Alibaba 学习圣经》 PDF
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
实现你的 linux 自由:
实现你的 网络 自由:
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
实现你的 分布式锁 自由:
实现你的 王者组件 自由:
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》