Google SRE
一个需要人工阅读邮件和分析警报来决定目前是否需要采取某种行动的系统从本质上就是错误的。
监控系统的 4 个黄金指标:延迟、流量、错误和饱和度。
对于 SRE 而言,自动化是一种力量倍增器,但不是万能的。当然,对力量的倍增并不能改变力量用在哪的准确性:草率地进行自动化可能在解决问题的同时产出其他问题。
自动化的情景通常是自动化管理系统的生命周期,而非系统内部的数据(修改系统中一些账户的某个子集的属性是相当罕见的):例如,部署新的服务集群。
Google 内部使用的监控系统与 Prometheus 非常相似。
白盒监控只能看到已经接受到的请求,并不能看到由于 DNS 故障导致没有发送成功的请求,或者是由于软件服务器崩溃而没有返回的错误。Google SRE 团队通常利用探针程序 (prober) 解决该问题,其使用应用级别的自动请求探测目标是否成功返回。
谷歌内部的每一个可执行文件中都默认包含一个 HTTP 服务,提供标准的监控接口。
我们强调至少将 SRE 团队 50% 的时间花在软件工程上,其余时间,不超过 25% 的时间用来 on-call,另外 25% 用来处理其他运维工作。
值得警惕的是,理解一个系统应该如何工作并不能使人成为专家。只能靠调查系统为何不能正常工作才行。—- Brian Redman
不要过早的归因于小概率事件,“当你听到蹄子声响时,应该先想到马,而不是斑马”
有很多方法可以简化和加速故障排查过程。可能最基本的是:
- 增加可观察性。在实现之初就给每个组件增加白盒监控指标和结构化日志。
- 利用成熟的、观察性好的组件接口设计系统。
使用唯一标识标记所有组件产生的所有相关 RPC。
前端服务器的负载均衡
搜索请求最重要的是延迟,而对于视频上传请求,用户已经预知该请求将要花费一定的时间,但是同时希望该请求能够一次成功,所以最重要的是吞吐量。需求不同,那么决定哪条线路是最优的也是不同的:
- 搜索请求发往最近的、可用的数据中心。评价条件是 RTT。
- 视频上传流采取另外一条路径,也许是一条目前带宽没有占满的链路,来最大化吞吐量,同时牺牲一定程度的延迟。
现实中,许多其他因素也在最优方案的考虑范围内,有些请求会发到稍远一点的数据中心,以保障该数据中心的缓存处于有效状态。
(1)DNS 负载均衡
DNS 返回多个 A 记录,客户端随机选择。有两个问题:
- 每条记录都会引来基本相同的请求流量
- 客户端无法识别最近的地址
优点就是最简单、最有效的负载均衡。
(2)虚拟 IP 负载均衡
负载均衡器如何决定选择哪个后端服务器接受请求:
- 选择负载最小的,有些请求可能必须使用同一个后端服务器,那么负载均衡器需要去跟踪经过它转发的连接,或者也可以
id(packet) % N
,然而N
变化的时候,几乎所有连接都要中断,更好的方法是采用一致性哈希算法:最小程度的减少影响。 - NAT 转换
- 修改目标 MAC 地址
数据中心内部的负载均衡系统
如何识别异常的 Backend: 当 Backend 的活跃请求超过 100 时,标记为不可用,但是有些后端可能有长连接,很长时间不回复,虽然可以调节限制的数量来规避这个问题,但是仍然不能解决根本问题:真的不健康,还是仅仅回复的有点慢。
Backend 的三种状态:
- 健康:可处理请求
- 拒绝连接:后端无响应,正在启动、停止或异常
- 跛脚鸭状态:明确要求客户端停止发送请求
跛脚鸭状态的 Backend 会广播给所有客户端,但是那些没有建立连接的客户端呢?Google 的 RPC 框架中,未建立 TCP 连接的客户端也会定期发送 UDP 心跳。
(1) 如何限制客户端所能连接的 Backend 的个数 ?
- 随机选择,每次把后端可用的
shuffle
一下,缺点:负载不均衡 - 确定性算法,每个后端接受同样数量的连接
def subset(backends, client_id, subset_size):
subset_count = len(backends) / subset_size
round = client_id / subset_count
random.seed(round)
random.shuffle(backends)
subset_id = client_id % subset_count
start = subset_id * subset_size
return backends[start:start + subset_size]s
(2) 如何进行负载均衡 ?
- round robin: 负载效果很差,因为请求处理成本不同 (所有客户端不会以相同的速率发送请求),机器物理差异,其他性能因素 (坏邻居、任务重启)
- 最闲轮询: 效果很差,客户端对每个后端的了解仅仅是它自己发送请求的一个情况
- 加权轮询: 每个后端有一个能力值,实践中效果比较好
应对过载
(1) 如何让客户端自己限流?
requests
: 发出的请求数量accepts
: 后端任务接受的请求数量K
越小,节流算法越激进,一般推荐K = 2
(2) 划分重要性
最重要、重要、可丢弃的,RPC 可以携带传递这些信息
(3) 客户端何时重试?
请求头包含重试计数器元信息,避免级联重试。
处理连锁故障
管理关键状态:利用分布式共识来提高可靠性
几个案例:
- 脑裂问题: 两组服务器,每组一个 leader 一个 follower,两组 leader 接受不到心跳,互相 kill 对方,结果可能同时为主,也可能都关闭。
- 需要人工干预的灾备切换
- 集群出现网络分区,两个分区各自选举出了一个 leader,同时接受 write 和 delete
上述所有问题应采用**分布式共识算法 (ZooKeeper、Consul、etcd)**来解决。
当连接闲置时间过长时,RPC 框架应将其转换为 UDP。