在Wandera,大多数微服务都是用Java编写的,并且都在全球众多的Kubernetes集群中运行。不久前,我们的一个团队注意到网络间歇性问题,这会导致我们两个边缘服务之间的通信失败。 解决方法很简单,将受影响的服务的流量故障转移到运行状况良好的实例上,并在一段时间后进行恢复过来,但是却无法及时发现问题,在线程转储中找不到任何暗示潜在问题的信息,后端团队引入了一个度量标准和一个警报,以检测边缘服务之间的断路(CB),以便至少我们可以及时做出反应而不会影响我们的客户。 在Wandera,我们不仅每天以连续交付的方式多次独立发布服务,而且还可以通过使用内部内置的功能标记系统以类似的方式为客户启用功能。该系统的一部分是我们的大多数Java服务使用的通用库,这些库用于检查是否为特定客户/管理员/设备/其他启用了特定功能。库是在一段时间之前编写的,并且在过去的几年中没有进行任何重大更改,因此您可以称其为经过验证的库。但是,就像所有内容一样,魔鬼会隐藏得很详细,环境或输入参数的变化可能会导致令人惊讶的结果。这将在以后发挥作用。 警报是在圣诞节之前引入的,相对安静,直到一月初。在这一点上,警报开始定期触发,并相应地将其上报给On-Call工程师(正确的做法是)。这似乎只发生在我们当时最繁忙的美国特区中。我们尝试更改在边缘服务旁边运行的本地数据库,并向群集的工作节点中添加了更多资源,但是即使服务的响应时间开始缩短,断路仍在发生。 我们有一些理论,但没有确凿的证据。例如,一种理论认为问题可能与新的数据库后端(从嵌入式到外部)有关,这带来了额外的延迟,并且无法很好地处理负载。我们的Prometheus指标显示了负载下数据库的延迟增加,而DB指标却没有任何增加。 尽管问题是断断续续的,并且影响相对较小,但我们还是决定召集一个专门tiger团队几天来共同关注该问题。
- 团队可以将问题隔离到我们的网络服务之一
- 它以某种方式与服务的发布或带来的额外负载有关
- 锁定后,该服务将无法恢复,除非完全从负载中取出负载
- FF检查会产生大量垃圾(并且在解析时会占用宝贵的CPU周期)
- 为了使Java能够应对,必须调用GC
- 更多的GC意味着更高的服务延迟
- 更高的延迟意味着处理请求需要更多的线程
- 更多线程→更多上下文切换
- 更多上下文切换→需要更多CPU周期
- CPU达到容器限制
- 容器被节流
- 没有足够的CPU进行垃圾收集
- 内存回收速度不够快,垃圾堆积
- 需要更多的垃圾收集意味着需要更多的CPU
- CPU不足→CPU节流
- CPU节流→延迟增加
- 需要更多线程和GC
- 恶性循环现在已经完成
- 服务现在(几乎)无响应
- 检查FF所花费的CPU骤降了97%
- 每秒分配的Java堆内存减少了80%
- 最后,服务的平均响应时间缩短了60%
- 花费几个小时或几天的时间全神贯注于此问题可能是有效的
- 在Kubernetes中对容器设置了限制后,始终会发出有关CPU节流的警报
- 代码中的每个抽象都有一个代价,衡量其效果,以免失控
- 遥测(日志,指标,跟踪)很重要,但在某些情况下还不够
- 在生产中测量profile服务很重要