从1到1000W:哔哩哔哩直播架构演进史
如果无法正常显示,请先停止浏览器的去广告插件。
1. 从0到1000W
哔哩哔哩直播架构演进史
Bilibili 林恩
2. 本期内容
Bilibili 直播发展历程
技术专题讨论
• 从0到1 • 热点数据
• 微服务化 • 流量放大
• 容器化 • 活动保障
• 从 PHP 到 Golang
• 网关架构
3. Bilibili 直播发展历程
2014 年 Bilibili 直播成立
2015 年 直播上线
2016 年 直播用户明显上涨
2017 年 微服务化
2018 年 服务容器化
2019 年 Golang 服务化
2020 年 网关架构
2021 年 S11 Bilibili 直播 全站 1000 W 在线
2022 年 S12 再创辉煌
4. Part 1. 直播的从0到1
5. 直播平台:从0到1
流媒体服务器
• 直播的推流、转码、拉流等媒体数据处理
长连接服务器
• 实时推送弹幕、送礼、通知等消息到用户终端
应用服务器
• 直播业务系统服务,如注册、登录、开播、送礼、
充值、结算等
6. 直播平台:从0到1
直播业务系统有多复杂?
在2017年之前直播业务代码都保存在一个live-app-web 的项目中。
如今上述系统已经演化成了由 200+服务构成的超级复杂的生态。
7. 微服务的前夜:局座来了
2016年7月13 日 局座(张召忠)首次来 B 站直播,引发热议
由于观众数量远超直播服务承载能力,导致直播卡顿、页面打
开缓慢,整个直播系统几乎处都在崩溃的状态。
8. 微服务的前夜:反思
活动保障能力 服务治理能力
• 容量评估 • 熔断/降级/限流
• 应急预案
单体服务缺陷
• 鸡蛋都在一个篮子里
• 服务容量易达到上限(木桶的短板)
9. Part 2. 微服务化
10. 微服务化: 垂直拆分 & 水平拆分
拆分方式
• 垂直拆分:对业务进行分类,将不同的业务划分到不同的应用中,如主播业务、用户业务
• 水平拆分:将业务进行分层,如将营收业务拆分成送礼服务、订单服务、钱包服务
高内聚&低耦合
• 相关性高的功能在一个服务
• 禁止双向依赖
• 同步+异步
• 调用链不宜过深,一般2/3层
11. 微服务规范
• 按照直播业务领域将直播业务划分为用户业务、主播业务、营收活动业务
• 每个微服务拥有自己独立的数据库、缓存
• 每个微服务仅能访问自己的数据库、缓存,服务间的数据交换通过 API 或消息队列
• 每个微服务都需要有明确的负责人,对服务的稳定性负责
12. 微服务方案&组件
服务框架:基于 Swoole 开发的微服务框架
通信协议:TCP 上封装的 RPC 协议,服务间通过 TCP 短连接进行接口调用
服务发现:部署 zookeeper 作为服务注册、发现组件,并开发了一个叫做 Apollo 的伴生程序管理服务
注册、健康检查、配置拉取等功能
配置管理:将 zookeeper 作为配置中心,可以通过后台管理每个服务的配置实现动态下发和生效
消息队列:搭建专门的 kafka 机器作为消息队列,由于 PHP 直接跟 Kafka 交互较为复杂,我们搭建了专
门的投递代理服务 publisher 和消息回调通知服务 notify
统一网关:所有外部流量都经过一个网关服务转发到对于的业务服务,在这层统一网关上实现流量转
发、URL重写、超时、限流、熔断、缓存、降级等等流量治理能力
13. 从单体到微服务的拆分过程
14. 从单体到微服务的拆分过程:数据库在线拆分
数据库拆分逻辑与微服务类似:
• 垂直拆分,按照业务归属将原来在同一个库的不同表拆分到不同的数据库
• 水平拆分,按照分表键将数据拆分到不同的数据库、表(分库分表)
如何进行在线服务不停机的数据库迁移?
• 基于 MySQL 自增步长的方案
• 通过设置新、旧集群主键自增步长实现双写双向复制无冲突
• 服务代码无更改,三次重启和配置修改完成数据库切换
15. 从单体到微服务的拆分过程
16. 2017.12.22 局座第二次来B站
17. Part 3. 容器化
18. 为什么要容器化
物理机部署的缺陷: 容器化:
• 服务端口冲突,需要分配端口和目录隔离 • 每个 POD 有自己的 IP
• 多个服务间存在资源竞争和互相影响 • 可以限制 POD 的资源使用
• 压测结果干扰因素大,容量评估准确度低 • 可以通过压测分析系统容量、发现瓶颈
• 环境独立、不会互相影响
19. 服务容器化踩过的坑
两种 CPU 调度方式:
• CPUSET:绑核模式,每个 POD 通过 CPU 亲和性设置固定到几个 CPU 上执行,由于是固定分配可以
实现资源独占,相对的整体资源使用率会偏低。
• CFS:即完全公平调度,通过将 CPU 时间片分配的方式进行调度,按时间分片比较灵活,也很容易实
现资源超配来提升利用率。
PHP 服务在 CFS 模式下超时非常严重,因此在当时的情况下我们选择了
CPUSET 的调度模式。
20. PHP服务超时的原因
• 进程数过多:PHP 服务是以多进程的模式运行,在原有物理机部署下 worker 进程数量设置很大,在迁
移到容器后同样的 worker 数量导致进程过多,调度变慢
• CGROUP 泄露:大量无用cgroup 容器未删除、kmem BUG触发业务删除的容器泄漏,两类问题可同时
存在,导致系统中cgroup memory子系统数量较多,在业务场景cadvisor,kubelet等进程频发读取
mem_cgroup信息时竞争导致软中断耗时高,业务模块延迟变高,可以通过内核升级解决该问题。
• CPU Burst:在内核上优化 CPU 的调度方式,通过类似令牌桶的 CPU 限流方式解决频繁 CPU 突刺产
生的超时问题。当容器的 CPU 使用低于 quota 时,可用于突发的 burst 资源累积下来;当容器的 CPU
使用超过 quota,允许使用累积的 burst 资源。最终达到的效果是将容器更长时间的平均 CPU 消耗限制
在 quota 范围内,允许短时间内的 CPU 使用超过其 quota。
21. 请求上的突发流量处理
在直播场景中存在大量不易察觉的流量突刺,这类突刺由于时间较短很难在 Prometheus 监控上发现,但
却产生了非常多的超时错误。
解决方案:
1. 扩容
2. 限流
3. 突发流量隔离
22. 请求上的突发流量处理
Default group :资源固定分配
Turbo group:资源混用
流量在网关层根据 QPS 进行分流,将超过
阈值的流量引入 Turbo group 进行处理。
对请求进行打标,具有相同 group 属性的
优先调度。
23. Part 4. 从 PHP 到 Golang
24. PHP 服务在大流量下的问题
PHP 服务是通过 swoole 实现的服务框架,采用多 worker 的方式处理请求,性能由单个请求耗时和 worker
数量决定。
在实际的业务中我们碰到了这样一些问题:
• PHP 的多进程同步模型极易因为单个下游异常而导致整个服务挂掉,因为下游响应变慢 PHP Worker 不
能及时释放,新的请求来了之后只能排队等待空闲 Worker,这样的级联等待进而导致系统雪崩。
• 实现 RPC 并发调用实现较为复杂,在一些业务复杂的场景由于只能串行调用下游接口,导致最终对外的
接口耗时非常高。
• PHP 服务扩容带来了数据库、缓存连接数的压力,当时还没有成熟的数据库代理,而是每个 PHP
Worker 都会直连数据库,这直接导致了连接数爆炸,进一步限制了 PHP 服务的扩容能力。
25. Golang 在 B 站
毛老师作为 Golang 布道师,很早便在 B 站进行 Golang 生态建设,发展到 2018 年已孵化出 Kratos、
Discovery、Overlord 、Config 等一系列 Golang 微服务中间件,并取得不错的应用效果。
主要特性:
• 协程实现并发请求
• 超时传递
• P2C 负载均衡
• BBR 限流
• GRPC 服务
26. 基于 Golang 微服务的新架构
服务分为三类角色:interface、service、job
• interface :业务网关按业务场景进行划分,如
App、Web 网关,在网关内完成对应场景的 API
接入,对下游业务服务的数据聚合、App 版本
差异处理、功能模块降级等。
• service :业务服务按业务领域划分,如房间服
务、礼物服务,不同的业务服务完成各自的业
务逻辑。
• job :业务JOB是依附于业务服务的,通常是用
于定时任务处理、异步队列消费等场景。
27. Part 5. 网关架构
28. 网关架构:流量网关 & 业务网关
统一网关:管控所有接入层流量
• 熔断/降级/限流/转发/多活/灰度/录制
• 鉴权/WAF
业务网关:面向业务功能和客户端实现的 API 组
合和适配
• API 聚合、组装、客户端版本兼容
• 功能开关、数据缓存、业务降级
API 聚合:单一场景下将原有数十个 API 调用通
过网关聚合成 1~2 个接口,降低客户端首屏时延
和处理复杂性。
29. TOPIC 1.数据热点
30. 数据热点的产生
一场令人期待的的游戏比赛
一场期待已久的晚会
或是一次产品发布会
31. 读数据热点
在这些热门活动中面临高在线、高互动的挑战,房间内的所有用户都会访问的数据即成为我们所说的热点
热点的风险: 热点的应对:
• 单节点性能瓶颈 • 多级缓存 (CDN、Redis缓存、内存缓存)
• CPU 过高/流量过高/活跃现场数过多 • 考虑数据一致性、时效性
32. 写数据热点
热点房间同时存在热点写的场景,如同时向主播送礼、关注主播等。均涉及到对同一条记录的频繁修改。
应对方案:
• 同步转异步,将同步写转换为消息队列的投递,由异步程序消费消息进行处理
• 聚合消费,异步消费中将具有统一特征的的数据进行聚合处理写入,如用户积分、关注数的变化可以
将 +1、+1、-1聚合为一次 +1 操作
• 分库分表,分散写入
33. TOPIC 2.流量放大
34. 场景1:请求返回了不需要的字段
典型的例子:
业务场景只需要用户头像和用户名,接口返回了全量的用户信息
影响:
• 带宽消耗、耗时增加、部分字段有额外读取计算成本
处理方案:
• GraphQL ,字段选择器
• GRPC,FieldMask
• API 数据模块化
35. 场景2:流量放大
我们观察到一次用户页面访问会对某些服务产生数倍到数十倍的请求放大。
用户进入房间后除了请求房间服务,在请求其他业务服务时会再次请求房间服务。
解法:
• 断开非必要的依赖,对于内部服务由调用方通过参数传递所依赖的信息。
36. 场景 3:小服务难以承受之重
一个小众功能(100 QPS)因为需要在房间页露出,导致他需要承担房间页的 QPS (1W+)
这合理吗?这不合理
类似于空缓存的思维,我们可以将业务属性抽象为标签(TAG),当对象拥有该标签才请求对应的业
务下游。
经过这样一个小小的优化,我们减少了 80% 无
效请求
付费直播、语音连线、PK、互动游戏。。。
37. TOPIC 3.活动保障
38. 专题讨论3:活动保障
活动保障由哪几部分工作构成?
• 场景分析
• 容量评估
• 活动预案
• 现场保障
那什么最重要?
所有环节都重要,任何一环掉链子都可能导致最终
的事故。
39. 场景梳理
场景梳理的目的?
• 识别保障业务域
• 识别功能保障优先级
• 识别依赖的服务和资源
• 识别依赖的上下游团队和人员
场景梳理需要梳理哪些东西?
• 用了什么功能
• 功能由哪些服务提供
• 服务是哪些接口
• 这些接口依赖了哪些服务
• 功能、服务、接口按等级进行划分
40. 容量评估
容量评估分为两部分,当前的系统容量、预期的系统容量
当前的系统容量通常可以通过压测直接得到,通常压测会分三轮
• 第一轮当前容量摸底
• 第二轮基于流量预估对系统进行扩容部署后压测
• 第三轮对优化后的系统进行复压、验证
流量预估的两种方式:
• 归纳法,可以通过过去的近似场景来预估本次
的流量。
• 演算法,通过用户行为路径和系统实现方式进
行演算得到,结合流量转化模型估算对系统的
压力。
压测方法:
• 接口压测
• 场景压测
• 全链路压测
41. 活动预案
预案是针对活动保障场景出现的各类 case 的操作手册。
针对可能出现的各类异常场景提供对应的 SOP
• 接口超时
• 缓存故障
• 数据库故障
• 流量超限
• 磁盘写满
活动预案需要与对应场景和负责人关联,能够与告警关联的预案需要直接与告警关联,能够自
动执行的预案一定要通过程序自动执行。
所有预案都需要经过演练、测试以验证其可用性。
42. 现场保障
现场保障 VS 日常值班
• 信息集散、统一分发
• 分级调度、不重不漏
• 在线值守、实时响应
• 数据记录、演练复盘
43. 总结
架构是逐渐成长和进化的过程,每一个阶段需要优先解决当前阶段的主要矛盾
故障驱动技术升级,如何从故障中总结反思并推动架构、系统升级非常关键
系统复杂度不可避免,通过分层设计来降低系统复杂度