当前位置: 首页 > news >正文

Go语言的gRPC教程-超时控制

前言

一个合理的超时时间是非常必要的,它能提高用户体验,提高服务器的整体性能,是服务治理的常见手段之一

一、为什么要设置超时?

用户体验:很多RPC都是由用户侧发起,如果请求不设置超时时间或者超时时间不合理,会导致用户一直处于白屏或者请求中的状态,影响用户的体验

资源利用:一个RPC会占用两端(服务端与客户端)端口、cpu、内存等一系列的资源,不合理的超时时间会导致RPC占用的资源迟迟不能被释放,因而影响服务器稳定性

综上,一个合理的超时时间是非常必要的。在一些要求更高的服务中,我们还需要针对DNS解析、连接建立,读、写等设置更精细的超时时间。除了设置静态的超时时间,根据当前系统状态、服务链路等设置自适应的动态超时时间也是服务治理中一个常见的方案。

二、客户端的超时

连接超时

还记得我们怎么在客户端创建连接?

conn, err := grpc.NewClient("localhost:8090",grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithUnaryInterceptor(clientUnaryInterceptor),)
if err != nil {panic(err)
}

如果目标地址127.0.0.1:8090无法建立连接,grpc.Dial()会返回错误么?答案是不会的,grpc.NewClient() 立即返回一个 *grpc.ClientConn,不等待连接建立。连接是异步后台建立的,如果连接没有创建成功会在下面的RPC调用中报错。

2.1 服务调用的超时

和上面连接超时的配置类似。无论是普通RPC还是流式RPC,服务调用的第一个参数均是context.Context

所以可以使用context.Context来控制服务调用的超时时间,然后使用status来判断是否是超时报错,关于status可以回顾之前讲过的错误处理

// 服务调用的超时, 3秒内调用完成, 否则超时, 并返回超时错误ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()// 2. 调用方法resp, err := client.Search(ctx, &helloworld.SearchRequest{Request:  "Golang",Keywords: []string{"hello", "world"},})if err != nil {st, ok := status.FromError(err)if !ok {log.Println(err)return}switch st.Code() {case codes.DeadlineExceeded:fmt.Println("服务调用超时:", st.String())default:log.Printf("Unhandled error : %s ", st.String())}return}

2.2 拦截器中的超时

普通RPC还是流式RPC拦截器函数签名第一个参数也是context.Context,我们也可以在拦截器中修改超时时间。错误处理也是和服务调用是一样的

需要注意的是context.WithTimeout(context.Background(), 100*time.Second)

func clientUnaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {start := time.Now()// 拦截器中的超时, 2秒内调用完成, 否则超时, 并返回超时错误var cancel context.CancelFuncctx, cancel = context.WithTimeout(ctx, 3*time.Second)defer cancel()err := invoker(ctx, method, req, reply, cc, opts...)if err != nil {st, ok := status.FromError(err)if ok && st.Code() == codes.DeadlineExceeded {panic(err)}fmt.Println("调用失败:", err)}fmt.Printf("client interceptor 耗时: %d\n, 客户端操作系统: %s\n, method: %s", time.Since(start), cos, method)return err
}

三、服务端的超时

3.1 连接超时

服务端也可以控制连接创建的超时时间,如果没有在设定的时间内建立连接,服务端就会主动断连,避免浪费服务端的端口、内存等资源

s := grpc.NewServer(grpc.ConnectionTimeout(3*time.Second),
)

3.2 服务实现中的超时

服务实现函数的第一个参数也是context.Context,所以我们可以在一些耗时操作前对context.Context进行判断:如果已经超时了,就没必要继续往下执行了。此时客户端也会收到上文提到过的超时error

func (server *HelloWorldServer) Search(ctx context.Context, r *helloworld.SearchRequest) (*helloworld.SearchResponse, error) {// 超时判断,ctx.Done() 表示上下文已取消, 超时, 或者手动取消select {case <-ctx.Done():return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")default:}return &helloworld.SearchResponse{Response: fmt.Sprintf("hello %s", r.GetRequest()),}, status.New(codes.OK, "success").Err()
}

很多库都支持类似的操作,我们要做的就是把context.Context透传下去,当context.Context超时时就会提前结束操作了

db, err := gorm.Open()
if err != nil {panic("failed to connect database")
}db.WithContext(ctx).Save(&users)

3.3 拦截器中的超时

在服务端的拦截器里也可以修改超时时间

// serverUnaryInterceptor 服务端一元拦截器
func serverUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {start := time.Now()// 拦截器中超时控制var cancel context.CancelFuncctx, cancel = context.WithTimeout(ctx, 5*time.Second)defer cancel()log.Printf("Method: %s, req: %s, resp: %s, latency: %s\n",info.FullMethod, req, resp, time.Now().Sub(start))return resp, err
}

四、超时传递

一个正常的请求会涉及到多个服务的调用。从源头开始一个服务端不仅为上游服务提供服务,也作为下游的客户端

在这里插入图片描述

如上的链路,如果当请求到达某一服务时,对于服务A来说已经超时了,那么就没有必要继续把请求传递下去了。这样可以最大限度的避免后续服务的资源浪费,提高系统的整体性能。

grpc-go实现了这一特性,我们要做的就是不断的把context.Context传下去

// 服务A
func main(){ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second)defer cancel()client.ServiceB(ctx)
}
// 服务B
func ServiceB(ctx context.Context){client.ServiceC(ctx)
}
// 服务C
func ServiceC(ctx context.Context){client.ServiceD(ctx)
}

在每一次的context.Context透传中, timeout都会减去在本进程中耗时,导致这个 timeout 传递到下一个 gRPC 服务端时变短,当在某一个进程中已经超时,请求不会再继续传递,这样即实现了所谓的 超时传递

关于超时传递的实现可以参考下面的参考资料中的链接

五、总结

通过使用context.Context,我们可以精细化的控制gRPC中服务端、客户端两端的建连,调用,以及在拦截器中的超时时间。同时gRPC还提供了超时传递的能力,让超时的请求不继续在链路中往下传递,提高链路整体的性能。

参考

[1]示例代码 : gitee

[2] https://segmentfault.com/a/1190000043583545

http://www.dtcms.com/a/321670.html

相关文章:

  • XXL-JOB多实例
  • 「ECG信号处理——(22)Pan-Tompkins Findpeak 阈值检测 差分阈值算法——三种R波检测算法对比分析」2025年8月8日
  • 宁商平台税务新政再升级:精准施策,共筑金融投资新生态
  • 创建MyBatis-Plus版的后端查询项目
  • 构网型逆变器三相共直流母线式光储VSG仿真模型【simulink实现】
  • 影刀 —— 练习 —— 读取Excel的AB两列组成字典
  • 【数值积分】如何利用梯形法则近似求解积分
  • Nearest Smaller Values(sorting and searching)
  • 专题二_滑动窗口_最大连续1的个数
  • 用户组权限及高级权限管理:从基础到企业级 sudo 提权实战
  • 基于 Vue + 高德地图实现卫星图与 Mapbox 矢量瓦片
  • Claude Code:智能代码审查工具实战案例分享
  • 流形折叠与条件机制
  • C++学习笔记
  • “鱼书”深度学习进阶笔记(1)第二章
  • 从零构建桌面写作软件的书籍管理系统:Electron + Vue 3 实战指南
  • 智慧农业温室大棚物联网远程监控与智能监测系统
  • Nginx反向代理教程:配置多个网站并一键开启HTTPS (Certbot)
  • git reset
  • Maven/Gradle常用命令
  • 14. isaacsim4.2教程-April Tags/给相机加噪声
  • GPT-5发布:AI竞赛进入新阶段
  • Spring Boot Redis 缓存完全指南
  • ApiPost 设置统一URL前缀
  • 计算机基础速通--数据结构·串的应用
  • 医防融合中心-智慧化慢病全程管理医疗AI系统开发(中)
  • 元数据管理与数据治理平台:Apache Atlas 构建与安装 Building Installing Apache Atlas
  • 有哪些产品需要遵循ASTM D4169-23e1
  • 【ee类保研面试】其他类---计算机网络
  • 操作系统:多线程模型(Multithreading Models)与超线程技术(Hyperthreading)