B站架构设计6:微服务可观测性设计
2021-09-11 20:14:00一、日志
1. 日志级别
Warning:
- 没人看警告,因为从定义上讲,没有什么出错。也许将来会出问题,但这听起来像是别人的问题。
- 我们应该尽可能的消除警告级别,它要么是一条信息性消息,要么是一个错误。
- 参考 Go 语言设计额哲学,所有警告都是错误,其他语言的 warning 都可以忽略,除非 IDE 或者在 CICD 流程中强制他们为 error,然后逼着程序员们尽可能去消除。
- 同样的,如果想要最终消除 warning 可以记录为 error,让代码作者重视起来。
Fatal,记录消息后,直接调用 os.Exit(1),这意味着:
- 在其他 goroutine defer 语句不会被执行;
- 各种 buffers 不会被 flush,包括日志的;
- 临时文件或者目录不会被移除;
- 不要使用 fatal 记录日志,而是向调用者返回错误。如果错误一直持续到 main.main。main.main 那就是在退出之前做处理任何清理操作的正确位置。
Error,也有很多人,在错误发生的地方要立马记录日志,尤其要使用 error 级别记录。
- 处理 error;
- 把 error 抛给调用者,在顶部打印日志;
如果您选择通过日志记录来处理错误,那么根据定义,它不再是一个错误——您已经处理了它。记录错误的行为会处理错误,因此不再适合将其记录为错误。
err := somethingHard() if err != nil { log.Error("oops, something was too hard", err) return err // what is this, Java ? } if err := planA(); err != nil { log.Infof("could't open the foo file", err) planB() }这里产生了降级行为,本质属于有损服务,更倾向在这里使用 Warning。
Debug,相信只有两件事你应该记录:
- 开发人员在开发或调试软件时关心的事情。
- 用户在使用软件时关心的事情。
显然,它们分别是调试和信息级别。
log.Info 只需将该行写入日志输出。不应该有关闭它的选项,因为用户只应该被告知对他们有用的事情。如果发生了一个无法处理的错误,它就会抛出到 main.main。main.main 程序终止的地方。在最后的日志消息前面插入 fatal 前缀,或者直接写入 os.Stderr。
log.Debug,是完全不同的事情。它由开发人员或支持工程师控制。在开发过程中,调试语句应该是丰富的,而不必求助于 trace 或 debug2(您知道自己是谁)级别。日志包应该支持细粒度控制,以启用或禁用调试,并且只在包或更精细的范围内启用或禁用调试语句。
B站如何设计和思考的:https://github.com/go-kratos/kratos/tree/v2.0.x/log
2. Logger
在 package 使用的时候
package foo
import “mylogger”
var log = mylogger.GetLogger(“github.com/project/foo”)
- foo 耦合了 mylogger
- 所有使用 foo 的其他库,被透明依赖了 mylogger
当我们使用 kit 时候
package foo
type logger interface {
Printf(string, ...inferface())
}
type T struct {
logger
// other fields
}
package foo
import "github.com/pkg/log"
type T struct {
logger log.Logger
}
延迟需要打日志的类型与日志的实际类型之间的绑定。
3. 日志选型
一个完整的集中式日志系统,需要包含以下几个主要特点:
- 收集-能够采集多种来源的日志数据;
- 传输-能够稳定的把日志数据传输到中央系统;
- 存储-如何存储日志数据;
- 分析-可以支持 UI 分析;
- 警告-能够提供错误报告,监控机制;
开源界鼎鼎大名 ELK stack,分别表示:Elasticsearch、Logstash、Kibana 它们都是开源软件。新增了一个 FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat 占用资源少,适合于在各个服务器上搜集日志后传输给 Logstash,官方也推荐此工具。
此架构由 Logstash 分布于各个节点上搜集相关日志、数据,并经过分析、过滤后发送给远端服务器上的 Elasticsearch 进行存储。
Elasticsearch 将数据以分片的形式压缩存储并提供多种 API 供用户查询,操作。用户亦可以更直观的通过配置 Kibana Web方便的对日志查询,并根据数据生成报表。
因为 logstash 属于 server 角色,必然出现流量集中式的热点问题,因此不建议使用这种部署方式,同时因为还需要做大量 match 操作(格式化日志),消耗的 CPU 也很多,不利于 scale out。

此种架构引入了消息队列机制,位于各个节点上的 Logstash Agent 先将数据/日志传递给 Kafka,并将队列中消息或数据间接传递给 Logstash,Logstash 过滤、分析后将数据传递给Elasticsearch 存储。最后由 Kibana 将日志和数据呈现给用户。因为引入了 Kafka,所以即使远端 Logstash server 因故障停止运行,数据将会先被存储下来,从而避免数据丢失。
更进一步的:
将收集端 logstash 替换为 beats,更灵活,消耗资源更少,扩展性更强。

3. 日志系统
设计目标:
- 接入方式收敛;
- 日志格式规范;
- 日志解析对日志系统透明;
- 系统高吞吐、低延迟;
- 系统高可用、容量可扩展、高可运维性;
格式规范,JSON作为日志的输出格式:
- time: 日志产生时间,ISO8601格式;
- level: 日志等级,ERROR、WARN、 INFO、DEBUG;
- app_id: 应用id,用于标示日志来源;
- instance_id: 实例 id,用于区分同一应用不同实例,即 hostname;
设计与实现,日志从产生到可检索,经历几个阶段:
- 生产 & 采集
- 传输 & 切分
- 存储 & 检索
4. 采集
logstash:
- 监听 tcp/udp
- 适用于通过网络上报日志的方式
filebeat:
- 直接采集本地生成的日志文件
- 适用于日志无法定制化输出的应用
logagent(自研):
- 物理机部署,监听 unixsocket
- 日志系统提供各种语言 SDK
- 直接读取本地日志文件

logagent设计

容器内应用日志采集:基于 overlay2,直接从物理机上查找对应日志文件。

5. 传输
基于 flume + Kafka 统一传输平台
基于 LogID 做日志分流:
- 一般级别
- 低级别
- 高级别(ERROR)
现在B站替换为 Flink + Kafka 的实现方式。

6. 切分
从 kafka 消费日志,解析日志,写入elasticsearch
bili-index: 自研,golang 开发,逻辑简单,性能 高, 可定制化方便。
- 日志规范产生的日志(log agent 收集)
logstash: es 官方组件,基于 jruby 开发,功能强大, 资源消耗高,性能低。
- 处理未按照日志规范产生的日志(filebeat、logstash 收集),需配置各种日志解析规则。

7. 存储和检索
elasticsearch多集群架构:日志分级、高可用
单数据集群内:master node + data node(hot/stale) + client node
- 每日固定时间进行热 --> 冷迁移
- Index 提前一天创建,基于 template 进行mapping 管理
- 检索基于 kibana

8. 日志文件
使用自定义协议,对 SDK 质量、版本升级都有比较高的要求,因此我们长期会使用“本地文件”的方案实现:
采集本地日志文件:位置不限,容器内 or 物理机
配置自描述:不做中心化配置,配置由 app/paas 自身提供,agent 读取配置并生效
日志不重不丢:多级队列,能够稳定地处理日志收集过程中各种异常
可监控:实时监控运行状态
完善的自我保护机制:限制自身对于宿主机资源的消耗,限制发送速度
二、链路追踪
1. 设计目标
- 无处不在的部署
- 持续的监控
- 低消耗
- 应用级的透明
- 延展性
- 低延迟
2. Google Dapper
参考 Google Dapper 论文实现,为每个请求都生成一个全局唯一的 traceid,端到端透传到上下游所有节点,每一层生成一个 spanid,通过traceid 将不同系统孤立的调用日志和异常信息串联一起,通过 spanid 和 level 表达节点的父子关系。
核心概念:
- Tree
- Span
- Annotation

调用链:在跟踪树结构中,树节点是整个架构的基本单元,而每一个节点又是对 span 的引用。虽然 span 在日志文件中只是简单的代表 span 的开始和结束时间,他们在整个树形结构中却是相对独立的。
核心概念:
- TraceID
- SpanID
- ParentID
- Family & Title

追踪信息:
- 追踪信息包含时间戳、事件、方法名(Family+Title)、注释(TAG/Comment)。
- 客户端和服务器上的时间戳来自不同的主机,我们必须考虑到时间偏差,RPC 客户端发送一个请求之后,服务器端才能接收到,对于响应也是一样的(服务器先响应,然后客户端才能接收到这个响应)。这样一来,服务器端的 RPC 就有一个时间戳的一个上限和下限。
植入点:Dapper 可以以对应用开发者近乎零浸入的成本对分布式控制路径进行跟踪,几乎完全依赖于基于少量通用组件库的改造。如下:
当一个线程在处理跟踪控制路径的过程中,Dapper 把这次跟踪的上下文的在 ThreadLocal中进行存储,在 Go 语言中,约定每个方法首参数为 context(上下文)
覆盖通用的中间件&通讯框架、不限于:redis、memcache、rpc、http、database、queue。

3. 链路追踪
架构图:

追踪消耗,处理跟踪消耗:
- 正在被监控的系统在生成追踪和收集追踪数据的消耗导致系统性能下降,
- 需要使用一部分资源来存储和分析跟踪数据**,**是Dapper性能影响中最关键的部分:
- 因为收集和分析可以更容易在紧急情况下被关闭,ID生成耗时、创建Span等;
- 修改agent nice 值,以防在一台高负载的服务器上发生 cpu 竞争;
采样:如果一个显着的操作在系统中出现一次,他就会出现上千次,基于这个事情我们不全量收集数据。
有意思的论文:Uncertainty in Aggregate Estimates from Sampled Distributed Traces
固定采样,1/1024:这个简单的方案是对我们的高吞吐量的线上服务来说是非常有用,因为那些感兴趣的事件(在大吞吐量的情况下)仍然很有可能经常出现,并且通常足以被捕捉到。然而,在较低的采样率和较低的传输负载下可能会导致错过重要事件,而想用较高的采样率就需要能接受的性能损耗。对于这样的系统的解决方案就是覆盖默认的采样率,这需要手动干预的,这种情况是我们试图避免在 Dapper 中出现的。
应对积极采样:我们理解为单位时间期望采集样本的条目,在高 QPS 下,采样率自然下降,在低 QPS 下,采样率自然增加;比如1s内某个接口采集1条。
二级采样:容器节点数量多,即使使用积极采样仍然会导致采样样本非常多,所以需要控制写入中央仓库的数据的总规模,利用所有 span 都来自一个特定的跟踪并分享同一个 traceid 这个事实,虽然这些 span 有可能横跨了数千个主机。
对于在收集系统中的每一个 span,我们用hash算法把 traceid 转成一个标量Z ,这里0<=Z<=1,我们选择了运行期采样率,这样就可以优雅的去掉我们无法写入到仓库中的多余数据,我们还可以通过调节收集系统中的二级采样率系数来调整这个运行期采样率,最终我们通过后端存储压力把策略下发给 agent 采集系统,实现精准的二级采样。
下游采样:越被依赖多的服务,网关层使用积极采样以后,对于 downstream 的服务采样率仍然很高。
API:
- 搜索:按照 Family(服务名)、Title(接口)、时间、调用者等维度进行搜索
- 详情:根据单个 traceid,查看整体链路信息,包含 span、level 统计,span 详情,依赖的服务、组件信息等;
- 全局依赖图:由于服务之间的依赖是动态改变的,所以不可能仅从配置信息上推断出所有这些服务之间的依赖关系,能够推算出任务各自之间的依赖,以及任务和其他软件组件之间的依赖。

- 依赖搜索:搜索单个服务的依赖情况,方便我们做“异地多活”时候来全局考虑资源的部署情况,以及区分服务是否属于多活范畴,也可以方便我们经常性的梳理依赖服务和层级来优化我们的整体架构可用性。

- 推断环依赖:一个复杂的业务架构,很难避免全部是层级关系的调用,但是我们要尽可能保证一点:调用栈永远向下,即:不产生环依赖。
4. 经验 & 优化
性能优化:1、不必要的串行调用;2、缓存读放大;3、数据库写放大;4、服务接口聚合调用;
异常日志系统集成:如果这些异常发生在 Dapper 跟踪采样的上下文中,那么相应的 traceid 和 spanid 也会作为元数据记录在异常日志中。异常监测服务的前端会提供一个链接,从特定的异常信息的报告直接导向到他们各自的分布式跟踪;
用户日志集成:在请求的头中返回 traceid,当用户遇到故障或者上报客服我们可以根据 traceid 作为整个请求链路的关键字,再根据接口级的服务依赖接口所涉及的服务并行搜索 ES Index,聚合排序数据,就比较直观的诊断问题了;
容量预估:根据入口网关服务,推断整体下游服务的调用扇出来精确预估流量再各个系统的占比;
网络热点&易故障点:我们内部 RPC 框架还不够统一,以及基础库的组件部分还没解决拿到应用层协议大小,如果我们收集起来,可以很简单的实现流量热点、机房热点、异常流量等情况。同理容易失败的 span,很容易统计出来,方便我们辨识服务的易故障点;
opentraceing:标准化的推广,上面几个特性,都依赖 span TAG 来进行计算,因此我们会逐步完成标准化协议,也更方便我们开源,而不是一个内部“特殊系统”;
三、监控
Monitoring:
- 延迟、流量、错误、饱和度
- 长尾问题
- 依赖资源 (Client/Server 's view)
opentracing (Google Dapper):
- jaeger
- zipkin
Logging:traceid关联
Metric:Prometheus + Granfana
涉及到 net、cache、db、rpc 等资源类型的基础库,首先监控维度4个黄金指标:
- 延迟(耗时,需要区分正常还是异常)
- 流量(需要覆盖来源,即:caller)
- 错误(覆盖错误码或者 HTTP Status Code)
- 饱和度(服务容量有多“满”)
系统层面:
CPU,Memory,IO,Network,TCP/IP 状态等,FD(等其他),Kernel:Context Switch
Runtime:各类 GC、Mem 内部状态等
线上打开 Profiling 的端口;
使用服务发现找到节点信息,以及提供快捷的方式快速可以 WEB 化查看进程的 Profiling 信息(火焰图等);
watchdog,使用内存、CPU 等信号量触发自动采集;
四、统一数据采集传输
1. 传输设计
传统离线大数据架构中的分层:
- ODS:操作数据层,保存原始数据
- DWD:数据仓库明细层,根据主题定义好事实与维度表,保存最细粒度的事实数据
- DM:数据集市/轻度汇总层,在DWD层的基础之上根据不同的业务需求做轻度汇总
ODS 是离线数仓建设第一层,对数据的准确性、实时性要求较高。同时实时数仓也依赖传输,因此我们比较早实现了实时传输体系。

事件(event)主要来源:
- 移动端
- 系统:startup, fingerprint
- 技术:tracking, apm, probe/monitor
- 埋点:expose, click, pv, adhoc
- 服务端
- 指标:metric (prometheus)
- 日志:audit, info/warn/error
- 链路追踪:tracking log
- 埋点:精细化埋点

模型设计:
- 传输模型:
- logid:即事件,一个 logid 代表一个或者一组 event source。
- 协议模型:
- 移动端:移动端行为事件模型(北极星),把一组 event 整合为一个 event source,使用一个 logid 描述,彼此之间使用 eventid 区分。(区分了其他移动端事件类型,使用其他 logid)
- 服务端:定义了日志、链路追踪的 logid ,统一的 schema。服务端埋点 schema 难统一,不同的 event source 对应不同的 logid。

协议设计:
- 协议演进
- v1.0:
- \1 分隔符
- key=value&key1=value1
- v2.0:
- protobuf -> parquet(Google Dremel)
- recrodio
- v1.0:
- recordio goals:
- 埋点、日志、网络传输,一套格式和工具
- 简单可实现,支持多语言 SDK
- 具备 meta 能力
- 高性能,简化序列化,避免二次序列化

架构设计 & 痛点:
- Gateway:
- 协议统一:HTTP & gRPC => gRPC
- logid 路由:kafka topic
- payload:batch & compress
- Kafka:
- topic 设计:通道(sink)vs 业务 vs logid
- Collector:
- 分布式分区感知
- 多路输出
- 自定义 ETL
- Exactly-Once

2. 边缘计算
动态 CDN 加速:为了传输链路的质量和速率,使用动态CDN 加速用户最后一公里的请求。移动端的 event 批量压缩上报到边缘节点后,再透传回到源站,源站 Gateway 负责接受数据投递给 Kafka。但存在痛点:
- 海量请求透传
- 源站回源带宽高
- 快速扩容较难
- 源站压缩开销大
系统设计中,存在中心化、热点,我们应该参考微服务的核心理念:去中心化。

边缘计算:源站的 Gateway 资源扩容难,容易直面流量热点,因此引入 Edge 替换旧的加速 CDN,在边缘处理“更多”的任务。
- 聚合压缩
- 批量回源
- 上下行带宽复用
- 弹性扩缩容
更进一步的,引入 BFE Agent 后数据反压的暂存可以上移,解决 Gateway 的状态(state),使之成为无状态服务。

Logagent:Logagent 部署在每一个 node(物理机)上,负责 node 和 container 的日志(event)采集。
和 BFE Agent 类似,最终都成为进入 Gateway 流量前的代理角色。两类 Agent 数量级都远远多于 Gatway 的节点数量,因此把 Gateway 用于反压 Kafka 数据的 FileChannel 移除,全部抛给上游 Agent,成为我们治理 Gateway 架构的核心思路。
全局来看,数据反压:Collector -> Kafka -> Gateway -> Agent -> Client

3. Flink
一个可靠的 Collector 需要具备如下特性,对于需要 hardcode 方式扩展,对比 Flink 维护成本比较高:
- 运维成本
- 弹性扩缩容
- 分区感知
- ETL 扩展(小文件合并、DAG 任务)
- Sink 列化适配
- Exactly-once
对于实时流式数据传输落地,本质上就是流式计算,基于 Flink 比较符合直观。

Exactly-once:当 Sink 端出现故障,可能需要重新处理一些数据。Flink 提供的 checkpoint 机制,结合 Source/Sink 端配合支持 Exactly-once 语义,以 Hive 为例:
从 Kafka 消费数据,写入到临时目录
ck snapshot 阶段,将 Offset 存储到 State 中,Sink 端关闭写入的文件句柄,以及保存 ckid 到 State 中
ck complete 阶段,commit kafka offset,将临时目录中的数据移到正式目录
ck recover 阶段,恢复 state 信息,reset kafka offset;恢复 last ckid,将临时目录的数据移动到正式目录

分区感知:Hive 分区生成的难点在于如何确定分区的数据是否就绪以及如何添加分区。由于 Sink 端是并发写入,同时会有多个Task 写同一个分区的数据,只有当所有 Task 分区写入完成,才能任务就绪:
- watermark 标识数据处理进度,满足单调递增特性
- 所有 task 的 watermark 随数据流传递到 commit 算子
- committer 算子拿到所有 task 的最小处理时间,得到全局最小处理时间,并以此作为 Hive 分区的最小就绪时间
- 当最小就绪时间更新时,可判断是否增加 Hive 分区
类似 DAG 思路,同样可以用于合并小文件。

加速 DWD:部分实时指标使用 Kappa 架构完成计算,少量关键指标(比如金额相关)使用 Lambda 架构用批处理重新计算,增加一次校对过程。离线计算中,ODS 已经实时落地,离线使用,同样的 DWD 也可以根据需要可以同一套口径计算 加速 DM/ADS 数仓的产出时间。加速 DWD 层用于两个场景:
- 离线计算依赖 DWD
- 实时计算依赖 DWD

4. 稳定性

管道隔离:理论上来说每个 event stream 全管道隔离效果最好,一个 logid 对应一个 kafka topic 再关联一个 flink job 来进行消息 sink。
- 上千条流(任务)需要运营和维护
- 对基础设施要求比较高
根据不同阶段演进,tradeoff:
- 单 topic + 高中低优先级
- 部门级 topic + 优先级
- 部门级 topic + 业务线 topic + 优先级
- logid 级 topic

kafka topic 中混有多路 logid,早期使用简单的 rr 负载写入到 partition 中,带来了堆积等问题:
- 单 Sink 通信 HDFS 文件1000 * 0.2 * 24 + 1000 * 0.8 = 5.6k
- Namenode 压力大,共计文件数:5.6k * 800 = 448W
参考了 Google Consistent Hashing with Bounded Loads,最终文件数量降低了10倍。
大流量 logid 均分到各个 partition,小流量 logid 命中某几个 partition,局部不均,整体均匀的策略。

kafka:受限 Kafka 集群上各种热点情况,IO相互影响导致RT过高,如:
- consumer 热点
- broker 热点
- partition 盘级别热点
吞吐决定了延迟,改进:
- cluster 延迟问题:failslow at scale,workload balancer
- client 级别的 基于 rt 的负载均衡策略或黑名单机制
- 基于熔断的 gutter kafka,用于接管自动修复系统运行过程中的负载

五、Linux 性能观察工具
Linux性能观测工具用于实时或离线分析系统运行状态,帮助定位瓶颈、优化资源利用率。它们涵盖 CPU、内存、磁盘I/O、网络等多个维度,可分为基础监控工具、高级分析工具和可视化/调优工具。
基础监控工具常用于快速获取系统健康状况:
- uptime:查看系统运行时长与平均负载。
- top/htop:实时显示进程CPU、内存占用,htop支持彩色界面与交互操作。
- vmstat:监控内存、进程、I/O、CPU等整体情况。
- iostat:分析CPU与磁盘I/O性能。
- free:查看内存与交换分区使用情况。
- dstat:整合 vmstat、iostat 等功能,彩色输出更直观。
高级分析工具适合深入排查性能问题:
- pidstat:按进程统计CPU、内存、I/O使用。
- iotop:实时显示进程磁盘I/O负载。
- sar:全面的系统活动报告工具,可输出历史数据。
- strace:跟踪进程系统调用,分析异常行为。
- tcpdump:抓取并分析网络数据包。
- perf:内核级性能分析,支持热点函数定位、cache miss分析。
- eBPF工具集(bcc/bpftrace):内核态高性能追踪,支持自定义探针与可视化。
可视化与调优工具提升观测效率:
- Grafana + Prometheus:结合exporter实现全栈监控与告警。
- Flame Graphs:基于 perf 或 eBPF 生成火焰图,快速识别热点路径。
- Collectl:可交互或 Web 方式展示 CPU、内存、网络等多维度数据。
建议:
- 实时监控用 htop*、*dstat;
- 历史分析用 sar、perf;
- 内核级追踪用 eBPF 工具;
- 可视化结合 Grafana 等平台。
这些工具配合使用,可构建从秒级实时观测到长期趋势分析的完整性能监控体系。

六、References
https://dave.cheney.net/2015/11/05/lets-talk-about-logging
https://www.ardanlabs.com/blog/2013/11/using-log-package-in-go.html
https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html
https://dave.cheney.net/2017/01/23/the-package-level-logger-anti-pattern
https://help.aliyun.com/document_detail/28979.html?spm=a2c4g.11186623.2.10.3b0a729amtsBZe
https://developer.aliyun.com/article/703229
https://developer.aliyun.com/article/204554
https://developer.aliyun.com/article/251629
https://www.elastic.co/cn/what-is/elk-stack
https://my.oschina.net/itblog/blog/547250
https://www.cnblogs.com/aresxin/p/8035137.html
https://www.elastic.co/cn/products/beats/filebeat
https://www.elastic.co/guide/en/beats/filebeat/5.6/index.html
https://www.elastic.co/cn/products/logstash
https://www.elastic.co/guide/en/logstash/5.6/index.html
https://www.elastic.co/cn/products/kibana
https://www.elastic.co/guide/en/kibana/5.5/index.html
https://www.elastic.co/guide/en/elasticsearch/reference/5.6/index.html
https://blog.aliasmee.com/post/graylog-log-system-architecture/
七、B站 SRE
1. 方法论
- 确保长期关注研发工作:所有的产品事故都应该有总结,无论有没有触发报警
- 在保障服务 SLO 的前提下最大化迭代速度:错误预算,发布策略
- 监控系统:alert、ticket、logging
- 应急事件处理:MTTF + MTTR
- 预案 playbook 最佳方法
- 变更管理:70%的生产事故来自变更而触发
- 采用渐进式的发布机制
- 迅速而准确地检测到问题的发生
- 当发现问题时,安全迅速地回退变动
- 需求预测和容量规划:自然增长 + 非自然增长
- 必须有一个准确的自然增长需求预测模型,需求预测的时间应该超过资源获取的时间
- 规划中必须有准备的非自然增长需求来源的统计
- 必须有周期性的压力测试,以便准备地将系统原始资源与业务容量对应起来
- 资源部署:变更管理与容量规划的结合物
- 效率与性能
- 持续的优化资源利用率,有效地降低系统的总成本
- 根据一个预设的延迟目标部署和维护足够的容量
我是一名软件工程师,这是我如何来应付重复劳动的办法
2. Oncall
- 面向终端用户的服务,时间为5分钟。而非敏感的业务通常来说是30分钟
- 多个渠道可以收到报警(不限于邮件、短信、自动电话呼叫)
- 响应时间和业务的可靠性有关,如果服务为99.99%,那么每个季度有13分钟的不可用时间,所以 oncall 工程师要分钟级响应生产事故
- 一旦收到报警信息,工程师必须确认(ack),要能够及时定位问题并且尝试解决问题,必要的话要升级(escalate)请求支援
- 一般主 oncall 人值班,副 oncall 作为辅助,通常团队也可以彼此作为副 oncall,互相值班,共同分担工作压力
- oncall 值班过程中,轮值工程师必须有足够的时间处理紧急事件和后续跟进工作,例如写事故报告
- 在面临挑战时,一个人会主动或者非主动选择下联方式处理:
- 依赖直觉,自动化、快速行动
- 理性、专注、有意识的进行认知类活动
- 当处理负载系统问题时,第二种行事方式是更好的,可能产生更好的处理结果,以及计划更周全的执行过程
- 凭直觉操作和快速响应看起来都是很有用的方法,但是这些方法都有自己的缺点。在有足够数据支撑的时候按步骤解决问题,同时不停地审核和验证目前所有的假设
- Oncall 可以寻求外部帮助
- 清晰的问题升级路线
- 清晰定义应急事件处理步骤
- 无指责,对事不对人的文化氛围
- 系统太稳定,容易松懈,定期轮值以及进行灾难恢复演习
3. 有效的故障排查手段
- 通用的故障排查过程 + 发生故障的系统足够了解
- 排查过程反复采用“假设- 排除”
- 收到报警时,先搞清问题的严重程度
- 对于大型问题,立即声明一个全员参与的会议
- 大不多数的人第一反应是立即开始故障排除过程,试图尽快找到问题根源,正确做法是:尽最大可能让系统恢复服务,止损
- 快速定位问题时:保存问题现场,比如日志、监控等
- 监控系统记录了整个系统的监控指标,良好的 Dashboard 可以方便快速定位问题,比如 Moni
- 日志是另外一个无价之宝,日志记录每个操作的信息和对应的系统状态可以让你了解在某一个时刻整个组件究竟在做什么,比如 Billions
- 链路追踪工具,比如 Dapper
- Debug 客户端,以便了解这个组件在收到请求后具体返回了什么信息
- 最后一个修改:一个正常工作的系统,直到某种外力因素出现。
- 一个配置文件修改,用户流量的改变,检查最近对系统的修改可能对查找问题根源很有帮助

4. 紧急事件响应
- 不要慌,你不是一个人在战斗!
- 如果你感到自己难以应付,就去找更多人参与:通知公司内的其他部门目前情况
- 经常性的进行灾难处理和应急响应演习:大型测试中一定先测试回滚机制
- 应急响应要让其他人得到清晰和及时的事态更新
- 如果你想不到解决办法,那么就再更大的范围内需求帮助。找到更多的团队成员,寻求更多的帮助,但是要快。
- 紧急事件过后,别忘了留出一些时间书写事故报告。
- 向过去学习,而不是重复它
- 没有什么比过去的事故记录是更好的学习材料了,公布和维护时候报告
- 在记录中请务必诚实,事务巨细,时刻寻找如何能在战术以及战略上避免这项事故的发生。
- 确保自己和其他人切实完成事故中总结的代办事项。
5. 紧急事故管理
- 无流程管理的紧急事故
- 过于关注技术问题
- 沟通不畅
- 不请自来
- 事故总控、事务处理团队、发言人
- 什么时候对外宣布事故
- 是否需要引入第二个团队来帮助处理问题?
- 这次事故是否正在影响最终用户?
- 在集中分析一小时后,这个问题是否依然没有得到解决?
- 划分优先级:控制影响范围,恢复服务,同时为根源调查保存现场
- 事前准备:事先和所有事故处理参与者一起准备一套流程
- 信任:充分相信每个事故处理参与者,分配职责后让他们主动行动
- 反思:在事故处理过程中注意自己的情绪和精神状态。如果发现自己开始惊慌失措或者干到压力难以承受,应该需求更多的帮助
- 考虑替代方案:周期性地重新审视目前的情况,重新评估目前的工作是否应该继续执行,还是需要执行其他更要要或者更紧急的事情
- 练习:平时不断地使用这项流程,知道习惯成自然
- 换位思考:上次你是事故总控负责人吗?鼓励每个团队成员熟悉流程中的其他角色
6. 事后总结:从失败中学习
- 避免指责,提供建设性意见
- 用户可见的宕机时间或者服务质量降低程度达到一定标准
- 任何类型的数据丢失
- on-call工程师需要人工介入的事故(包括回滚,切换用户流量等
- 问题解决耗时超过一定时间
- 监控问题(预示着问题是由人工发现的,而非报警系统)
重要!重要!重要!:不懂 SRE 的程序员不可能成为优秀程序员!
