微服务高可用秘诀 - 超时控制
超时控制在本质上就是快速失败。 几乎所有的软件项目都有超时控制策略,比如 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 连接的超时策略