你了解微服务的超时传递吗?
为什么需要超时控制?
很多连锁故障的场景下的一个常见问题是服务器正在消耗大量资源处理那些早已经超过客户端截止时间的请求,这样的结果是,服务器消耗大量资源没有做任何有价值的工作,回复已经超时的请求是没有任何意义的。
超时控制可以说是保证服务稳定性的一道重要的防线,它的本质是快速失败(fail fast),良好的超时控制策略可以尽快清空高延迟的请求,尽快释放资源避免请求的堆积。
服务间超时传递
如果一个请求有多个阶段,比如由一系列 RPC 调用组成,那么我们的服务应该在每个阶段开始前检查截止时间以避免做无用功,也就是要检查是否还有足够的剩余时间处理请求。
一个常见的错误实现方式是在每个 RPC 服务设置一个固定的超时时间,我们应该在每个服务间传递超时时间,超时时间可以在服务调用的最上层设置,由初始请求触发的整个 RPC 树会设置同样的绝对截止时间。例如,在服务请求的最上层设置超时时间为3s,服务A请求服务B,服务B执行耗时为1s,服务B再请求服务C这时超时时间剩余2s,服务C执行耗时为1s,这时服务C再请求服务D,服务D执行耗时为500ms,以此类推,理想情况下在整个调用链里都采用相同的超时传递机制。
如果不采用超时传递机制,那么就会出现如下情况:
- 服务A给服务B发送一个请求,设置的超时时间为3s
- 服务B处理请求耗时为2s,并且继续请求服务C
- 如果使用了超时传递那么服务C的超时时间应该为1s,但这里没有采用超时传递所以超时时间为在配置中写死的3s
- 服务C继续执行耗时为2s,其实这时候最上层设置的超时时间已截止,如下的请求无意义
- 继续请求服务D
如果服务B采用了超时传递机制,那么在服务C就应该立刻放弃该请求,因为已经到了截止时间,客户端可能已经报错。我们在设置超时传递的时候一般会将传递出去的截止时间减少一点,比如100毫秒,以便将网络传输时间和客户端收到回复之后的处理时间考虑在内。
进程内超时传递
不光服务间需要超时传递进程内同样需要进行超时传递,比如在一个进程内串行的调用了Mysql、Redis和服务B,设置总的请求时间为3s,请求Mysql耗时1s后再次请求Redis这时的超时时间为2s,Redis执行耗时500ms再请求服务B这时候超时时间为1.5s,因为我们的每个中间件或者服务都会在配置文件中设置一个固定的超时时间,我们需要取剩余时间和设置时间中的最小值。
context实现超时传递
context原理非常简单,但功能却非常强大,go的标准库也都已实现了对context的支持,各种开源的框架也实现了对context的支持,context已然成为了标准,超时传递也依赖context来实现。
我们一般在服务的最上层通过设置初始context进行超时控制传递,比如设置超时时间为3s
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel()
当进行context传递的时候,比如上图中请求Redis,那么通过如下方式获取剩余时间,然后对比Redis设置的超时时间取较小的时间
dl, ok := ctx.Deadline()
timeout := time.Now().Add(time.Second * 3) if ok := dl.Before(timeout); ok { timeout = dl }
服务间超时传递主要是指 RPC 调用时候的超时传递,对于 gRPC 来说并不需要要我们做额外的处理,gRPC 本身就支持超时传递,原理和上面差不多,是通过 metadata 进行传递,最终会被转化为 grpc-timeout 的值,如下代码所示 grpc-go/internal/transport/handler_server.go:79
if v := r.Header.Get("grpc-timeout"); v != "" { to, err := decodeTimeout(v) if err != nil { return nil, status.Errorf(codes.Internal, "malformed time-out: %v", err) } st.timeoutSet = true st.timeout = to }
超时传递是保证服务稳定性的一道重要防线,原理和实现都非常简单,你们的框架中实现了超时传递了吗?如果没有的话就赶紧动起手来吧。
go-zero 中的超时传递
go-zero 中可以通过配置文件中的 Timeout
配置 api gateway
和 rpc
服务的超时,并且会在服务间自动传递。
之前的 一文搞懂如何实现 Go 超时控制 里面有讲解超时控制如何使用。
参考
《SRE:Google运维解密》
项目地址
https://github.com/zeromicro/go-zero
https://gitee.com/kevwan/go-zero
欢迎使用 go-zero
并 star/fork 支持我们!
微信交流群
关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
腾讯内部实践分享 | 千节点Alluxio 集群助力游戏 AI 业务
图片来源:Pexels 本文作者:郑兵、毛宝龙、潘致铮 Alluxio 是一个面向 AI 以及大数据应用,开源的分布式内存级数据编排系统。随着大数据和 AI 业务向 Kubernetes 等容器管理平台迁移,将 Alluxio 作为中间层,为数据查询及模型训练等场景加速,成为各厂商的首选方案。 Alluxio 在游戏 AI 离线对局业务中解决的问题可以抽象为:分布式计算场景下的数据依赖问题,传统的数据依赖的解决方式有: 镜像打包,这种方式隔离性比较好,但使用镜像缓存能力有限,数据更新频繁,每次都要重新打包部署镜像,停服务更换容器; 即用即下,整体架构简单,但每次访问数据,都需要从远端拉取,性能较差; 本地部署,数据本地性好,但解决权限问题和运营困难。 这几种方式,都有着各自的优缺点。引入 Alluxio,可以在成本没有增加太多的情况下,显著提升 AI 任务的并发上限,同时 AI 任务程序仍使用原有的 POSIX 方式访问 Alluxio,因此业务对存储系统的改变无感知。 本文主要介绍 Alluxio 在算力平台上与游戏 AI 特征计算业务的适配和优化。 背景 游戏 AI 离线训练业务分...
- 下一篇
SQLBuilder.Core v2.2.7 已经发布,NET Standard 2.1 版本的 SQLBuilder
SQLBuilder.Core v2.2.7 已经发布,NET Standard 2.1 版本的 SQLBuilder 此版本更新内容包括: 优化 ConfigurationManager,支持 appsettings 自定义环境变量 “APPSETTINGS_ENVIRONMENT”; 优化仓储构造函数,添加 “configuration” 可选参数,用于支持自定义 I Configuration; 重命名 SetConfigurationFile -> SetConfiguration,重载 SetConfiguration; 新增 FormattableString 扩展类; 重载 IRepository 部分接口并实现,支持 FormattableString 内插 sql 语句; 移除 IRepository 的 Close 方法,以 Dispose 方法替代;新增 AutoDispose 属性、UseAutoDispose 方法; 优化仓储数据库连接释放逻辑,支持共享连接模式; 优化 AddSqlBuilder 扩展,新增 AddRepository、AddAllRe...
相关文章
文章评论
共有0条评论来说两句吧...