微服务高可用秘诀 - 超时控制

超时控制在本质上就是快速失败。 几乎所有的软件项目都有超时控制策略,比如 TCP 协议,有连接超时,写超时,读超时等等。 当我们的主机意外断电,或者被拔网线,内核来不及响应,调用方收不到 FIN 包,这时候的策略有:

  • 基于 TCP 的连接要支持应用级的心跳
  • 依赖 TCP 的 KeepAlive,超过 KeepAlive 的上限仍没有响应,TCP 会发送一个 ICMP 探针包问对方“你丫的还活着吗?”

微服务可用性第一道关,一定是良好的超时策略,只有让我们的微服务不堆积请求,尽快清空高延迟的请求释放 goroutine,我们的服务才不容易炸。只要我们的服务不炸,我们就有机会做降级、容错、熔断。

超时意味着服务线程耗尽,对 go 来说意味着 goroutine 耗尽,对项目来说就是 OOM。

latency SLO

服务提供者定义好 latency SLO(君子协定),更新到 gRPC Proto 文件中,服务后续迭代,都应保证 SLO。

service TestService {
	// latency SLO: 95th in 100ms,99th in 150ms
	rpc CreateXXX(CreateXXXRequest) returns (XXX);
}

超时控制的实现

当上游服务已经超时返回 504,而下游服务仍然在执行,会导致浪费资源做无用功。超时传递指的是把当前服务的剩余 Quota 传递到下游服务中,继承超时策略,进而做到全局的超时控制。 在 gRPC 框架中,会依赖 gRPC Metadata Exchange,基于 HTTP2 的 Headers 传递 grpc-timeout 字段,自动传递到下游,构建带 timeout 的 context。

监控

一般来讲,一个微服务的请求耗时呈双峰分布:95% 的请求耗时在 100ms 内,5% 的请求可能永不返回,所以评估接口的耗时要看 95 线,99 线。

如何设置合理的超时时间

这个一般要对微服务接口进行压测,设置一个合理的超时时间。

全链路的超时策略

从网关入口,一直到持久层都要配置好超时策略,如果漏配一个都有可能导致服务炸掉。

  • Nginx 的 proxy_timeout
  • DB 连接池配置超时
  • Redis 连接的超时策略