B站 SRE 发展的 5 年
B站2017年之前没有SRE,当时主要负责的事情就是效率优先,需求响应(比如变更、标准化、报警治理和琐事优化)。2018年引入SRE文化,开始理解业务架构、推进读的多活建设、探索 SRE 里的 Oncall 制度/复盘文化在B站的落地。
2019年正式进入落地,首先做了琐事优化(释放人力),将工单变更通过平台来自助审批。之后做了 SLO 体系探索,包括平台、基于 SLO 的服务分级体系、应急响应制度、复盘平台、问题管理制度建设等。
2020年 SRE 稳定性体系初步完善,20年B站做了很多活动(S10、跨晚、最美的夜等)。有了故障前的应急响应及故障后的复盘,开始探索故障前混沌工程和故障演练;质量之后是容量,B站是容器化部署(探索 PaaS 容量管理体系),在一系列落地之后,2020年稳定性体系初步完善。2021年至今SRE继续转型,从 Oncall 制度逐渐转向 BP 制度,更加关注稳定性和降本增效。之前做了多活、服务分级、SLO都会在今年重新做优化建设。全员现在在做 SRE 转型。
稳定性保障
下面介绍一下转型过程中具体的解析。首先是稳定性保障(高可用、多活、容量管理、活动保障)。
高可用
先看下整体架构。用户首先从 CDN 层到接入层,接入层有4层 LB/7层 SLB,还有逐渐推广的 API GW。
之后进到服务层,服务侧会有一个 BFF(服务网关),下面是 Service、Job、Admin等。
- 中间件主要是 MQ,数据同步 Canal、DTS,消息通知 Notify、缓存;
- 存储层面是关系型数据库、 KV、对象存储、ES等等;
- 再往下是可观测与效率体系;
- 底层是稳定性体系,包括服务分级&SLO、混沌工程/故障演练、多活容灾/预案管理、问题管理/事件分析、容量/降本及活动保障。
接入层
这是接入层架构,用户通过 DNS 或降级 HTTP DNS 访问 CDN,CDN 通过机房边缘 POP 点汇聚流量到机房,机房有不同可用区(逻辑概念),每个可用区主要是 SLB 和 API GW。
常见的故障有几种:
网络故障:边缘节点回机房(运营商公网故障);
机房故障:一般比较少遇到(主要看概率);
组件故障:比如 SLB 节点发生故障等;
服务故障:SLB/API GW 所代理的服务等;
接入层的高可用方案,比如 DNS 降级 HTTP DNS、多 CDN 节点动态选路、APP边缘节点网络层故障降级第三方CDN重试;多 POP 点做流量汇聚,多线路做源站互备。
如果单可用区 SLB 故障,支持 CDN 从其他可用区跨专线回源。
对于后端服务故障,SLB可发现服务区的所有节点;单可用区服务故障节点可以自动降级。SLB 也支持常见的降级、容灾与限流。
最后是 7.13 故障,SLB重建花了一个小时,故障后对 SLB 初始化重建做了很多优化,现在可以5分钟重建一套SLB。API Gateway 的高可用与 SLB 类似,这里不再重复。
服务层
上面说了南北向(接入层),现在来看看东西向。对 SRE 来说,这一层一定要了解(微服务架构),如果不了解一旦服务出现故障给不出业务故障止损预案。
服务调用是通过内部 Discovery 服务发现组件进行调用,服务部署了多活(可用区A和B),每个机房服务都是多节点部署,不同节点所运行的物理机(比如是CPU型号不一导致单核算力不一,要看 K8s 有没有做算力规划)。
这个过程可能出现问题,比如容器节点算力不一,服务B流量过载、可用区故障等,也有几种高可用能力:
- 服务调用有 P2C 算法,服务A调用服务B,服务B会返回自己节点的 CPU 使用率情况,服务A基于这次响应的 RT(包括服务B每个节点 CPU 使用率/成功率来选择一个最优的节点),虽然服务B不同节点 CPU 算力不一,但最终 CPU 使用率是一样的;
- 熔断,服务B故障时,服务A做一个熔断;
- 降级的场景有两种:
一是可用区的降级,当服务B在某可用区故障,服务A调用B可以通过服务发现组件降级到其他可用区;另外是做多活的时候,服务在本可用区没有做部署的情况下降级到其他可用区;
内容质量降级,服务B故障服务A可以访问本地缓存/降级服务。在我们场景里,如果播放地址失败会降级到灾备播放地址。B站首页APP推荐可能是动态的,推荐系统故障也会降级给用户热门视频。
- 限流有两种,一种是微服务框架侧全局限流,它在框架层面的拦截器,B站内部主要是 Golang,使用 Kratos框架,基于框架做了分布式限流。第二部分是动态限流,不需要预先配置,基于 Google 的 TCP BBR 算法来实现的,会自动分析每个容器节点 CPU 使用率/成功 QPS 等来自适应做一个保护。
中间件
服务还有一种故障场景是中间件的依赖。
看看核心服务对中间件依赖,比如服务写数据,对评论、弹幕数据可以接受最终一致,把写的数据扔到 MQ,用 Job 消费消息队列,写入DB。DB 层面会挂一个 Canal 组件,来消费数据库binlog,将消息写回消息队列,再刷到缓存里。
我们用到最常见的组件就是缓存/MQ/DB。
缓存在生产系统里有三种模式,Redis Cluster、Redis单节点/分布式、Memcache(内部是按这个顺序做推荐建议),每种语言在生产环境 SDK 都出现过各种各样的 Bug。
比如短连接风暴,之前搞拜年纪活动,活动一开始充电服务就故障了。后来发现,Jedis 的并发数量超过配置的 Total 之后,后续连接会变成短链接。redis单线程机制,长连接和和短连接性能相差10倍的数量级,一旦遇到短连接性能就会急剧下降。
我们用过各种语言,连 Redis 之后,往一个节点发一些cluster nodes 或者 slot 风暴这种请求,甚至可能会固定往一个节点发请求,很快把一个节点打死了。
当发生 cluster 风暴,再遇到短连接就会出现雪崩,根本没办法恢复。想恢复只能重建缓存或做一个冷重启。后来我们对缓存做了统一的 proxy 代理(sidecar 模式,或者通过 proxyless sdk),也是为了降本增效,把单容器改成 sdk 模式进行部署访问。
消息队列主要做临时持久化,异步写DB。集中式代理,不用 sidecar 模式部署,我们内部支持 redis 和 gRPC 两种协议。2020年B站做了很多扩圈活动,kafka topic数量急剧增加,当时代理是 kafka 的 Sarama SDK,这个 SDK 也是各种炸,不停出现故障。
数据库层面,内部提供数据库 proxy,用 sidecar 模式部署。对业务做读写分离,对业务透明。对异常SQL做拦截,比如慢SQL。为了降本增效,现在也提供了SDK模式。
前面说了很多架构层面的内容,SRE 一定要了解业务架构,如果不懂架构只能做日常需求(那和配管没区别),不懂业务架构的 SRE 是没有灵魂的。
那有了高可用能力,B站就不炸了吗?2021.7.13故障,B站/微博/知乎全网热搜,我们做一下复盘。
2021.7.13 故障
2021.7.13 晚 10:52,用户反馈B站无法使用,内部大量服务、域名接入层报警不可用。
10:57分,Oncall 同学发现 SLB 故障。
11:17,内网登陆也受影响,17分核心成员才陆续进入内网系统。
11:23分,读多活业务基本恢复。多活机房 SLB 容量过载,多活机房服务层正常/接入层挂了(容量涨了4倍左右)。
11点之后流量逐渐下降,重启恢复。当时直播业务也做了多活,首页接口没做多机房流量分发(所以没有恢复)。
11:17分登陆分析问题,开始怀疑是 SLB 组件问题,联系最近在SLB层面做了三次引擎 Lua 层的变更,也没有恢复。
此时面临2个选择,一是继续排查问题(底层Debug,SLB具体是什么故障?);二是给业务一个可以预期的止损手段。
当时选择新建一套 SLB,尝试隔离流量恢复业务。之后验证流量隔离是有效的。
1:00左右,新建 SLB 全部完成,开始做业务流量切换。
1:40左右,业务已经全部切到新集群,核心业务全部恢复。
这里能看到很多问题:
- 用户的链路和办公网链路未彻底隔离,11:17分才陆续进入内网系统,有些办公网后台直接放在公网对用户访问,办公网放到公网也会用到公网 SLB(没有彻底隔离)。
- 多活未按预期立即生效。多活机房SLB流量过来,只支持读,不支持写。
- 人力不足。故障止损和排查无法并行。1点左右新建,后面才尝试做故障排查。组件故障预案不完善、处理耗时久(集群的创建、配置的下发、公网 IP 配置花了接近1个小时)。
- 复杂事故影响面积大,故障定位成本高。
- 同时也看到,多活是机房级故障时容灾止损最快方案!
通过 7.13 故障能看到:
- 多活基架能力不足。机房与业务多活定位关系混乱,流量调度层面不支持精细基于用户特征做流量分片。
- 切量强依赖 CDN 平台,多活的接入层/用户流量调度分发层是在 CDN 层面实现,并没有做一个多活统一管控平台。
- 多活元信息也缺乏平台管理,比如哪个业务做了多活(多活是同城双活/异地容灾),业务有哪些规则支持多活。
高可用 - 多活
对于多活元信息定义做了标准化,业务多活 CRG 的定义(CRG 定义参考了蚂蚁的多活模式)。
- Gzone 服务(全局共享服务),可以在用户间共享数据,一个可用区写/其他可用区读(数据强一致性)。B站场景里,视频播放/番剧/影视/稿件信息/直播间信息数据都是平台型数据,这种数据在用户间共享不需要做单元化。
- Rzone 单元化业务(用户流水型数据),多可用区分片写/读,用户维数据在B站里就是社区类场景(评论/弹幕/动态等适合单元化的场景)。
- Czone 和 Gzone 有点类似,Czone 所有可用区都可以写/读(非单元化),介于 Gzone 和 Rzone 之间的,这种场景需要业务接受一定数据延迟和不一致。
对机房进行梳理,当时上海有很多机房(都在一个地域内,机房定位比较混乱)。梳理后将上海的4个机房划成两个逻辑可用区,同城双活(Gzone 类型服务)部署在上海;把江苏可用区作为 Rzone 单元化服务部署模拟异地;我们又规划华北/华南的可用区,来做单元化、Czone 服务部署(30ms-40ms网络延迟)。
高可用 - 同城双活
组件层面也做了优化。流量的 Router 是在 DCDN 层面,分发到各个机房,各个机房的服务在读写存储,主要是 KV、DB,Proxy 来做接入,同城双活的可用区只支持读,写都是通过 Proxy 转到主机房。通过 GZS 来做多活状态管理。
对这些组件优化主要是这几个:
- DCDN 支持用户 Mid、设备ID来做一致性 Hash,同时支持多机房动态权重。DCDN 是基于 Nginx 来构建的,后来重新开发支持多机房的动态权重。
- 服务原来的多活是不支持写的,没在多活机房做本地化部署和写链路感觉。核心链路一定要在本地写,微服务框架支持写感知,弱依赖服务回到主机房。
- 存储层面最早多活没有 Proxy 组件,需要业务自己决定哪个机房读/写(管理成本特别高)。我们后来统一做 Proxy 做业务接入管理。
- KV 存储原本不支持机房部署或者容灾切换,之后也做了改造优化。包括 TIDB 也支持多机房容灾切换。
- GZS 通过 GZS 来管理业务多活元信息、多活定义、切量编排/可视化。
GZS:Invoker
GZS 内部叫 invoker,基于 API 的元信息管理,结合内部 CMDB 系统(CMDB 存储业务元信息/审批平台),同时联动内部几个多活组件,比如 DCDN/KV存储/DB存储/API GW 组件来做流量切换。核心就是优化上面四部分:
- 多活规则元信息获取方式优化
- 平台多活和同城双活切量演练
- 多活编排、接入流程优化
- 审批、巡检、可观测能力加强
实际多活编排和切量流程是,编排先选择业务(评论/弹幕/播放)、选择业务类型(同城双活/单元化)、编排接入层规则。
数据库层编排是 KV、DB,要把多活各个维度资源编排出来,业务就可以发起切量。
业务和业务域。业务域(直播/电商/社区),业务域下会有业务(社区有评论/弹幕/点赞等),切量编排是对流量编排或存储编排。
切量完成之后触发审批。审批之后是巡检,巡检主要为三部分:
- 容量巡检,CPU/MEM;连接池,流量切到另一个机房,流量可能会翻倍;还有数据库/缓存连接池能否扛得住;限流,微服务框架也有很多限流配置,限流能否扛住。
- 延时巡检,主要做存储层面的巡检,现在是同城双活,延迟基本上没啥问题。
- 隔离巡检,存储有没有跨业务混用?比如评论的业务是否用一个动态/弹幕 DB、KV。
- 可观测,巡检指标可观测,连接池、限流是怎么变化的?业务多活流量规则,流量切换是否符合预期?是否把流量从1:1切到1:0;业务涉及核心应用SLO 指标。
除了质量之外,前面提到降本增效一定是今年很多互联网公司的目标,下面说如何通过容量管理做降本增效。
容量管理
容量管理之前,SRE 要想一个问题,要管什么容量?
- 在整个系统架构中有接入层(核心是带宽);
- 应用层面核心就是计算资源;
- DB 和存储,那是存储资源。
对 SRE 来讲,容量管理的核心应该是应用。
容量管理做出之后谁关注?
不同角色关注是不一样的,比如研发/SRE/平台/预算团队等等。
目标收益是做之前要想清楚的。这里是每个角色对容量管理的预期:
- 研发核心是有资源扩容、自动扩容、发布/回滚不被资源限制。
- SRE既要关注服务资源使用率,还要关注弹性扩缩,部门资源水位、降本增效。
- 部门维度,关注部门资源水位、使用率、部门成本、部门 TopN 服务报表。
- 平台维度,容器平台关注 Buffer、平台超卖、资源混部、资源利润率、降本增效。
- 成本团队就是资源用量、账单这些事情。
思考这些问题之后,内部围绕 K8s 形成了容量管理,右边是架构图。
底层是 K8s 平台,上层是基于 K8s 应用基础数据搜集(集群容量/资源池容量/Node容量/应用容量/应用基本画像);
再往上是VPA/HPA/合池/配额管理。VPA 是面向 SRE平台(对研发不可见),VPA 的好处是动态调整服务 Request 指标,提升 K8s 可调度资源数量;
HPA,横向弹性伸缩,面向研发,解决服务扩容的问题;
合池,Buffer 复用,增加弹性资源能力。要解决机器维度的差异或者 Node 节点的差异;
配额管理,没有合池每个部门只能看到独立资源的物理资源,合池之后看不到物理资源(只能看逻辑资源,LIKE云平台),Limit 作指标。
最上面就是容量可视化和报表运营,底层的元数据和容量保障之上给业务提供统一可视化平台/运营平台,查询部门资源容量/排序,某个组织/业务的容量和数据。
容量管理 - VPA
VPA 是降本增效的利器。首先看几个概念,在应用维度K8s有三个指标,一个是CPU Used(实际用了多少CPU,比如Limit 配了8个C,Request能配4个C,超卖的2倍,业务实际应用了2个c,Used 2c/Request 4c/Limit 8c;应用 CPU Limit/CPU Request=业务超卖率)。B站一开始超卖是应用维度超卖,就是给到研发自己配置。
遇到一些痛点,当 Request 分配完,Used 特别低时,资源池没有资源可调度,SRE找研发按应用维度调整超卖率,对研发来讲想抢占资源,这个服务是稳定的,不想把资源释放出来,就导致共识很差,效率很低,收益特别小。后来把VPA改成平台维度。
改造平台维度之后,研发不关注超卖率,只关注 Limit,不再感知 Request,VPA 的指标放到平台维度,业务实际用了多少 CPU/CPU total=物理资源 CPU 使用率。CPU Limit/CPU Total=资源池超卖率。CPU Request/CPU Used=资源冗余度。内部情况物理资源 CPU 使用率低于40%,会调整 VPA 策略,动态回收 Request,比如有一个业务部门资源使用率特别低,直接调整为 VPA 策略。
右边是K8s与VPA联动的架构图,上面是 VPA 管理平台:策略管理/数据运营/黑名单/预警。
比如某些业务不适合VPA,比如Job类型/推送服务(一天只运行5-10分钟),给这个服务做VPA没有任何意义。
做活动时整个资源池应用CPU使用率会增加,这个时候开启VPA,会导致第二天资源池分配的 CPU request 会增加,导致资源池没资源可用。
同时做 VPA 之后也对 VPA 做了很多预警策略,发现VPA是否符合预期。
中间是 VPA 与 K8s 联动 VPA 策略引擎。
底层是 K8s 提供的 VPA API。VPA主要解决两个场景。一是新增一个Pod,比如一台机器挂了,pod做一个漂移很常见,这种场景通过 K8s 的 APIServer 层面 Webhook 对所有新增的 Pod 做VPA;二是下发 VPA 策略对存量所有 Pod 做 updater,存量和新增的 Pod 都可以VPA。
规则按照这四个来:服务等级/服务画像/资源类型/度量指标。举个例子,内部L0服务,以CPU Used 7天P99指标,来 VPA 所有容器 CPU 资源,L2服务既要做CPU的VPA,也要做内存的 VPA。基于1天的指标来做就会更有利。
这是线上合池K8s资源使用率,有两个指标,一个是只看容器CPU使用率,另外是一看物理机维度CPU使用率怎么样,在8月初,物理机使用率达到50%以上,整个容器层面也可以超过40%。在VPA之前,我们一个资源池CPU使用率高峰期可以达到20%-30%就不错了。今年上半年都没有加资源,就靠VPA满足业务新增所有需求。
前面讲了高可用、多活,B站每年会有很多活动,比如S11、跨网拜年纪。2021年S11当天直播在线人数突破千万。
活动保障流程主要是活动了解->容量预估->压测演练->复盘清单->技保能力&预案。
前面讲了高可用、多活,B站每年会有很多活动,比如S11、跨网拜年纪。2021年S11当天直播在线人数突破千万。
活动保障流程主要是活动了解->容量预估->压测演练->复盘清单->技保能力&预案。
活动重保
活动开始前做活动了解
活动形式:直播/点播/秒杀/抽奖…
重点场景:不同活动重点不一样,弹幕互动、抽奖、送礼
活动对外链接:全链路保障
活动推送:站内推送、弹窗推送
活动后的场景:直转点、二次热点
时间线
容量预估
- 基础资源
交换机带宽、源站带宽
静动态CDN
- 业务资源
PaaS、laaS
Cache、MQ
压测&演练
内部压测分三轮:
第一轮:现有资源下压业务瓶颈;
第二轮:资源就绪后按照之前的预估人数做容量压测;
第三轮:所有优化和保障方案上线后压测演练。
演练层面两部分:
预案演练
故障演练:包括上下游依赖、中间件依赖、节点故障、HPA能力。
复盘清单
活动清单检查
关闭混部
关闭VPA
活动链路服务开启HPA
技保能力&预案
技术保障能力是侧重问题发生前的,你具备哪些高可用能力,有哪些能力保障业务稳定性。比如多活、跨机房降级、熔断等等,还有HPA、VPA等等。预案是问题发生之后应该怎么办,它们俩有不同侧重点,预案做攻击、容量、服务故障等三方面预案。
现场重保&复盘
看好监控大盘,把活动核心指标做定时同步,比如S赛,一场比赛结束之后就把这场比赛数据同步一下,包含前面讲的技术资源、业务资源、服务有没有出现一些高可用问题,比如有没有出现一些异常、限流。把活动中问题变更记录下来,复盘时不仅做活动当天复盘,还把活动从前期准备的问题统一复盘。
SLO 实践与反思
SLO
前面讲的是稳定性保障、高可用,服务可用性怎么度量?
我们先看一下 Google 的定义:
SLO 为服务可靠性设立了一个目标级别,它是可靠性决策的关键因素,是 SRE 实践的核心。
Google 甚至说没有 SLO 就没有SRE。为了使采用基于 SLO 的错误预算的可靠性工程方法,服务利益相关方认可 SLO,甚至服务可以达到 SLO。
基于 Google 的理论,B站基于现有分为两部分:服务分级与SLO系统。
服务等级分四级,L0-L3。对象包含业务(产品)=> 应用 => API。
业务就是产品,比如评论等级是 L0,评论下是应用,应用的等级不能超过产品等级。应用下有 API(发评论/拉评论),API的等级也不会超过应用的等级。
等级出来之后用于服务标准化、变革流程管控、报警测量,不同服务等级报警测量/稳定性要求不一样,比如服务有没有降级能力/多活能力。
SLO 系统提供的核心能力就是做 SLO 指标的选择/计算/定义。
服务分级&SLO
这是实践 SLO 的流程,首先创建业务&定级&定SLO;然后创建应用&定级;之后创建接口&定级&算SLI指标;最后基于接口聚合到业务上。
这种模型没有运转下去。
我们的反思:
- 分级模型太理想,定级成本非常高;
- 各种元信息关联不及时,数据准确率低;
- 开始做 SLO 计算只算公网服务,也只算了在接入层(SLB层)指标,单条 Metrics 没办法覆盖所有业务场景;
- 部分内网服务不对外,导致没有 SLI 数据;
- SLI 数据除了当报表外,也没人看
新的模式:
- 分级模型优化
- SLI模型优化
- SLO报警治理
说一下现在的玩法:创建一个业务只对业务主场景定级,比如评论核心就是发评论/拉评论(对这个场景定级)。应用不再需要定级,只需要打标。算 SLI 对多个对象/多条SLI指标做计算,某个API就是业务某一个具体波动的体现,也算它的指标数据。对业务指标,比如业务一些在线人数、播放量、发送的评论这是业务,我们会算三个维度的SLI指标。对这些指标做SLO的运营和质量运营。
仅度量SLI是没有任何意义的,核心是SLO报警和质量运营。
甚至SLO报警的重要性比质量运营更高,因为质量运营和SLI度量都不能帮业务提供什么东西,但 SLO 报警会帮业务第一时间发现问题,提升报警准确率。
事故定级
优化服务分级之后,也解决了事故定级中的问题。事故定级以前有两个指标,业务损失&服务等级:故障时间&PV损失,因为之前分级模型比较复杂,有业务/应用/接口,出现故障哪个等级作为故障的等级,我们经常在这里扯皮不清。
对业务分级做了优化之后,只对主场景定级,事故时看业务损失&业务定级&业务主场景系数。
主场景故障系数是故障功能对主场景影响程度,这种模型完全把服务分级复杂模型包进去,不用在故障时扯皮。
SRE的培养与转型
工作分层
工作分层:最上面是比较传统的日常答疑、变更、报警处理等。再往下是 SRE 横向的串联、协作、拉通,再往下是SRE或运维核心,对业务做支持,比如业务迁移重构&改造,中间件推广、技术运营、应急响应等等。基于上面三层抽象出稳定性体系,再将各种稳定性实践整合成稳定性体系。
对 SRE 的能力要求:
- 运维能力:基本运维能力、网络、OS/内核、架构能力
- 开发能力:工具开发、运维平台开发、工程能力等
- 合作共赢:项目管理能力、团队协作、同理心、情商与运营意识
- 个人潜质:学习能力、好奇心、逆向思维能力
- 责任心与担当。
培养与转型
SRE 培养有四方面:
- SRE文化:团队要认可SRE文化,从团队Title和组织名称可以看到对SRE的重视;从上到下传达SRE的转型;SRE职级序列;
- SRE方法论:学习 SRE 理论知识,主要是《SRE工作解密》《SRE工作手册》;
- SRE讨论会:SRE方法论专题分享。基于理论做实践落地。方法论掌握之后再拉大家做讨论会,讨论SRE的章节、内容,包括SRE稳定性、基础运维架构的知识;
- 开发转型:SRE 绕不开开发,内部也鼓励做开发转型,B站基于Golang,SRE 也是基于 Golang,鼓励 SRE 先做工具开发,能力达标之后会分配专业开发导师,参与 SRE 平台开发实践,最终开发平台又提升了SRE工作效率,实现正向循环。
作者简介
武安闯,2016年加入B站,深度参与B站微服务拆分、云原生改造、高可用建设、SRE转型和稳定性体系落地等项目。当前主要关注B站在线业务的SRE稳定性体系建设和推广,对SRE的实践有深入的探索与思考。