监控

4个黄金指标

  1. 请求的延迟
  2. 流量大小(qps)
  3. 错误
  4. 饱和度(系统的瓶颈指标,如cpu利用率、内存利用率),也可以理解为“水位”

保持简单

系统越简单,可以发生故障的地方就越少,就越可靠。而监控作为基础设施,即使在发生事故时也必须保持可用,此时失去监控无异于蒙眼狂奔。

prometheus + grafana + alertmanager的开源监控组合充分体现了谷歌对于监控系统的设计理念:

监控指标通过被监控实例上的一个http api暴露出来,由promethues定时收集并存储,而且其响应格式比json更加松散,容易解析,例如:

# HELP <metric_name> <help_text>
# TYPE <metric_name> counter
<metric_name>{<label_name>="<label_value>",...} <value>

控制台、告警系统拆分出来。每个模块都很独立、清晰、简单。

最小api

我们向API消费者提供的方法和参数越少,这些API就越容易理解,我们就能用更多的精力去尽可能地完善这些方法。同时,一个反复出现的主题是:有意识地不解决某些问题可以让我们能够更专注核心问题,使得我们已有的解决方案更好。在软件工程上,少就是多!一个很小的,很简单的API通常也是一个对问题深刻理解的标志。

发生事故时怎么办

先止损(回滚、切流量),然后再找原因和解决方案。

你可能会对几个可疑的点逐个排查,可以把测试手段和测试结果记录下来,一是能避免绕圈圈(人思绪混乱的时候是很容易犯这种错误的),二是能让他人及时了解到修复问题的进度,有助于提供帮助。

如何度量容量

google不推荐使用qps来度量容量,因为随着业务迭代,同样的qps带来的负载可能是差异巨大的。

google推荐用直接消耗的资源(cpu和内存)来度量容量,因为cpu占用率到达什么水平会导致性能恶化是比较稳定的。

甚至可以只用cpu。因为对于有gc的语言来说,内存压力会转化为cpu占用率。

而其他资源,如磁盘等,谷歌建议“给足,让它们几乎不会在cpu到达瓶颈之前到达瓶颈”。

但是,cpu并不能替代qps,qps仍然是度量流量大小最重要的指标,因为qps可以被直观地“切割”。比如说在某一时刻静态地来看,对于同一个负载均衡器后面的所有机器来说,qps大小就约等于负载大小。

过载

当流量超出单机的处理能力时,cpu负载升高导致处理请求的速度变慢,积压的请求会占用更多内存,内存不足导致频繁gc,进一步消耗cpu,机器性能开始断崖式下跌,此时即使把流量降低到正常的水平,机器可能也难以恢复正常状态了。

当单机发生过载时,健康检查可能会失败,负载均衡器会把流量引向其他机器,进而导致其他机器也进入过载状态。这是一个裂变式的过程,最终整个集群都无法提供服务。

当集群开始雪崩式过载时,人为降低集群的qps并不一定能解决问题,因为此时集群中健康的机器数量可能远少于平时。健康的机器数取决于以下3个因素:

  1. 系统重启机器的速度
  2. 机器达到性能巅峰的速度
  3. 机器在被打垮之前能坚持多久

避免过载的手段有如下几种,后文会展开讲解书中有独特见解的部分:

  1. 压测:知晓服务负载上限。
  2. 优雅降级:在服务过载时给客户端返回一个消耗资源更少的响应。
  3. 流量抛弃:当流量超出服务承受能力时,让请求快速失败。
  4. 静态容量规划 + 弹性伸缩:优雅降级和流量抛弃都是“在一个空间有限的屋子里尽可能多的塞东西”,而这个手段是“扩建房子”。

线程池队列管理

线程池线程数这个面试题你可能背的滚瓜烂熟,但是你可能没想过队列长度该设多大。

java中常见的Executors.newFixedThreadPool()给队列长度的默认大小就是Integer.MAX_VALUE,几乎不会出现拒绝任务的情况,大不了就是排很久的队。这非常适合不关心延时的离线任务。

但是,对于web或者rpc服务器这种关心请求延时的场景来说,请求排队比拒绝请求更可怕,因为如果队列无限长,一波流量高峰过后很久服务都无法正常处理新的请求,因为线程池还在一个一个地处理前面排队的请求,即使发起那些请求的用户已经放弃了。

google建议这种场景应该把线程池队列长度设为线程数量的一半甚至更少,如果一台机器已经无法及时处理新请求了,那就应该快速失败,好把负载转移到其他机器上。

当然,对于经常有突发流量的服务来说,应该结合处理请求的速度、超时时间、突发流量大小等因素合理地设置线程池队列长度。

流量抛弃

限制线程池队列长度是避免服务器过载的方式之一,还有以下其他几种方式:

  1. 基于当前cpu利用率决定是否抛弃请求。

  2. 优先抛弃低优先级的请求(google给内部rpc设置了4个优先级,以实现精准流量抛弃)

  3. 对排队的策略进行优化,默认策略是先进先出,改为先进后出或者使用可控延迟算法抛弃任务。这样可以针对性地抛弃那些因为等待过久而被用户放弃的请求。

    可控延迟算法(Controlled Delay,简称CoDel)是由Van Jacobson和Kathleen Nichols提出的一种主动队列管理(AQM)算法,旨在解决互联网中的“bufferbloat”问题。Bufferbloat是指当网络中的缓冲区长时间保持满状态时,会导致网络延迟增加,影响用户体验。

    CoDel算法的核心机制

    1. 目标停留时间(Target Sojourn Time)
      • CoDel算法使用5ms作为目标停留时间,即数据包在队列中的等待时间应尽量保持在5ms以下。这个值接近于零,以获得更好的延迟,但又不至于太小以致于队列会一直为空。
    2. 滑动测量窗口(Sliding Measurement Window)
      • CoDel使用100ms作为滑动测量窗口,用于检测队列中数据包的停留时间是否持续超过目标值。100ms是互联网流量的典型往返时间(RTT),如果拥塞持续时间超过100ms,可能会进入“坏队列”区域。
    3. 丢包机制
      • 当队列中数据包的停留时间超过目标值(5ms)并且持续时间超过100ms时,CoDel开始通过丢包(或显式标记拥塞通知)来减少队列。CoDel会逐渐增加丢包率,与丢包次数的平方根成正比,从而导致受影响的TCP连接的吞吐量线性下降,最终使队列清空,停留时间回落到目标值以下。

优雅降级

优雅降级通过给客户端返回一个所需资源更少的响应,以降低负载。例如搜索服务可以搜索内存中的不完整数据,而非磁盘上的所有数据。

但是使用时需要考虑以下几点:

  1. 什么条件下采用优雅降级?cpu利用率到多少时?延时到多少时?
  2. 优雅降级不应该经常触发,触发频率上升时应该发出告警。
  3. 由于不经常触发,降级逻辑的维护质量很可能不如正常逻辑,需要有定期演练保证优雅降级能正常工作。

重试

对于客户端来说,在rpc失败时重试是一种提升健壮性的简单有效的方式,但是对于服务端来说,在负载接近上限时,失败率上升,随之重试请求大幅上涨,加重了系统的负载,进入恶性循环。

为了避免这种情况的发生,可以使用以下手段对客户端重试进行限制:

  1. 使用指数型增长有随机抖动的重试周期,而不是固定的重试周期,计算过程如下:

    int expBackoff = (int) Math.pow(2, retryCount);
    int maxJitter = (int) Math.ceil(expBackoff*0.2);
    int finalBackoff = expBackoff + random.nextInt(maxJitter);
    

    这借鉴了计算机网络中TCP/IP的拥塞避免和控制的指数退避算法。指数型增长的重试周期有助于在请求失败次数上升时减少请求频率,降低服务端负载,随机抖动有助于避免多个客户端扎堆发起重试请求。

    你可以借助现成的重试工具实现这一点:https://github.com/rholder/guava-retrying

  2. 限制重试次数的上限,不要无限重试。谷歌给的建议是3次,因为如果一个请求3次都打到了异常的服务端实例上,这说明服务端集群总体处于不健康的状态,重试已经无济于事。

  3. 服务端返回的响应中,应该区分可重试和不可重试的错误。例如客户端收到参数错误、鉴权错误时就不应该再重试。

  4. 避免不同层次的重试叠加。否则高层的一个请求可能会导致底层rpc的重试次数以乘积式地增长,这会加重服务端的负载,并延长这个错误请求的处理耗时。

超时时间传递

常见的超时机制是:A调B,A为这个请求设置了一个时长t,如果B在t时间内无法返回,A就认为这次调用失败了。

这样简单的超时机制在调用栈很深的时候就会暴露出问题:每一层的超时时间没有一个明确的设置依据,最后的结果大多只能靠拍脑袋确定,而为了避免误伤正常请求,通常超时时间都会拍的偏大。这可能会造成一个高层的请求虽然已经因超时被放弃,但是底层仍然在花费资源处理这个已经被放弃的高层请求带来的调用。在系统濒临过载时,你肯定希望减少这种浪费。

谷歌提出:超时时间应当能够在链路上传递,例如一个请求的调用链路是A调B调C调D,A为这个高层请求设置一个总的超时时间10s,B这个环节花了8s,留给C和D的超时时间就只剩2s了,如果C花了3s,D在收到这个请求时就应该立即放弃处理该请求。

文章作者: 白烛魁
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 白烛魁的小站
运维
喜欢就支持一下吧