对于一个大流量互联网应用来说,系统的稳定性至关重要。可惜,稳定性目标并不那么轻易能够达成。现实中,种种意想不到的问题会出现。但是,本着专业的严谨,还是需要尽可能去规避解决各种问题,提前准备故障真实发生之后的处理手段。

故障类型

对于一个服务来说,可能遇到的故障类型有哪些呢?常见的有,

  • 自身代码问题造成故障,例如Full GC、死循环
  • 流量突增带来的故障,例如突发流量超出了系统容量水位
  • 依赖的上游服务故障,例如Nginx故障、网关故障
  • 依赖的下游服务故障,例如下游RPC服务、HTTP服务、外部服务故障
  • 依赖的中间件服务故障,例如Memcache/Redis/MySQL/Kafka等故障
  • 依赖的物理资源故障,例如虚拟机故障、网络故障

确定边界

服务端的稳定性由多方参与保障,运维、测试、开发、DBA等等角色。首先要明确的是对于问题的处理边界。从服务提供方的角度来说,哪些是需要处理也能够处理的问题呢?实际上,除了底层运行的物理资源故障之外,其它的都是应该纳入考虑范畴的。

治理思路

在明确了边界之后,怎么来具体实施?

  • 问题梳理。梳理,简单两个字,实际要投入很多精力与时间。从服务设计的架构流程角度出发,逐一排查可能的问题点。
  • 寻找方案。很多问题点的解决思路类似,所以这一环相对没有那么费劲。只要找到集中典型场景的策略,后续只是逐渐拓展覆盖面。
  • 实现方案。编码测试。这一步最为单纯,也最好看到进展。
  • 验证方案。方案有效性的验证也是很复杂的,需要去模拟各种问题。基础设施不完善的话,需要很多人力投入。

问题梳理

对于物理资源问题,服务自身没有什么处理能力。需要服务上游去处理或是外部工具处理。

对于代码问题,通过前置测试去排查,线上的处理策略也比较明确。有发布,则回滚。没发布,则隔离。不能隔离,则交由服务上游去处理。

对于流量问题,在流量压力超出系统允许水位时会带来问题,需要进行控制,或交由上游处理。

对于依赖问题,可以认为所有的依赖都可能出现问题。要梳理出系统的各种依赖点,

  • 下游RPC服务
  • 下游HTTP服务
  • 缓存服务
  • 数据库服务
  • 消息队列服务
  • ...

遇到的问题类型,

  • 访问异常
  • 访问超时

寻找方案

物理资源问题

  • 下线/替换故障节点。手动或自动,需要一定的基础设施支持。

代码问题

  • 测试前置,单元测试、集成测试。单元测试的覆盖率可以做一定要求,没有运行过的代码分支总是存在风险的。
  • 静态扫描。代码的静态分析,可以帮助养成一些好的编码习惯,排除问题隐患。
  • Code Review。关键代码的Code Review,可以考虑成发布流程的一环。
  • 发布卡点。在一些前置事项没有完成之前,禁止发布到线上。
  • 灰度发布。发布到线上之后,需要先灰度再全量。
  • 线上巡检。线上服务巡检要常态化,这样能快速发现问题。
  • 异常告警。通过异常告警来快速发现问题。
  • 快速回滚。很多时候,回滚真能解决问题。

流量问题

  • 扩容。从应用服务到存储服务都需要评估是否可以通过快速扩容来解决问题,如此最为便捷。
  • 降级。通过手动或自动手段,对服务降级,减少性能损耗。
  • 静态化。一些服务可以通过静态化手段来缓解性能问题。
  • 异步化。通过消息队列等手段,削峰处理。
  • QPS限流。如果能够提前确定各接口的容量水位,那么可以通过基本的限流手段来进行保障。
  • 自适应限流。可以考虑关于服务限流的一些思考中提到的自适应限流策略。

依赖问题

  • 熔断。对于出现问题的服务,需要能够识别下游故障情况,这种时候就需要降级熔断策略,当发现下游问题时,进行熔断,隔离故障影响。
  • 降级。降级到替代服务。

实现方案

具体要根据选择的策略进行实现,有必要去讨论的是,哪些可以通过现有的开源框架工具进行解决,哪些需要通过自主研发进行解决。

结果验证

  • 线下演练。在方案实现之后,首先需要在线下环境进行演练,发现问题,修改问题,最终获取结论。
  • 线上演练。方案的实际有效性需要在线上进行验证,否则无法形成闭环。

总结

稳定性是一个很复杂的问题,真实的稳定性治理是一个耗时耗力的过程。在那些基础设施完善的公司,可能已经能够常态化应对了。但更多的地方,想来也都是比较简单原始的。一步一步的解决治理过程中发现的问题,也是挺有意思的。且做且珍惜。虽然实际做下来发现,很多地方坑真的很深。