写给go开发者的gRPC教程-超时控制

‍‍


导言

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

为什么要设置超时

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

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

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

客户端的超时

连接超时

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

conn, err := grpc.Dial("127.0.0.1:8009",
    grpc.WithInsecure(),
)
if err != nil {
    panic(err)
}

// c := pb.NewOrderManagementClient(conn)

// // Add Order
// order := pb.Order{
//  Id:          "101",
//  Items:       []string{"iPhone XS", "Mac Book Pro"},
//  Destination: "San Jose, CA",
//  Price:       2300.00,
// }
// res, err := c.AddOrder(context.Background(), &order)
// if err != nil {
//  panic(err)
// }

如果目标地址127.0.0.1:8009无法建立连接,grpc.Dial()会返回错误么?这里直接放结论:不会的,grpc默认会异步创建连接,并不会阻塞在这里,如果连接没有创建成功会在下面的RPC调用中报错。

如果我们想控制连接创建时的超时时间该怎么做呢?

  • 异步转成同步:首先我们需要使用grpc.WithBlock()这个选项让连接的创建变为阻塞式的

  • 超时时间:使用grpc.DialContext()以及Go中context.Context来控制超时时间

于是实现如下,当然使用context.WithDeadline()效果也是一样的。连接如果在3s内没有创建成功,则会返回context.DeadlineExceeded错误

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

conn, err := grpc.DialContext(ctx, "127.0.0.1:8009",
 grpc.WithInsecure(),
 grpc.WithBlock(),
)
if err != nil {
 if err == context.DeadlineExceeded {
        panic(err)
    }
    panic(err)
}

服务调用的超时

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

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

ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

// Add Order
order := pb.Order{
 Id:          "101",
 Items:       []string{"iPhone XS", "Mac Book Pro"},
 Destination: "San Jose, CA",
 Price:       2300.00,
}
res, err := c.AddOrder(ctx, &order)
if err != nil {
 st, ok := status.FromError(err)
 if ok && st.Code() == codes.DeadlineExceeded {
  panic(err)
 }
 panic(err)
}

拦截器中的超时

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

需要注意的是context.WithTimeout(context.Background(), 100*time.Second)。因为Go中context.Context向下传导的效果,我们需要基于context.Background()创建新的context.Context,而不是基于入参的ctx

func unaryClientInterceptor(ctx context.Context, method string, req, reply interface{},
 cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {

 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
 defer cancel()

 // Invoking the remote method
 err := invoker(ctx, method, req, reply, cc, opts...)
    if err != nil {
        st, ok := status.FromError(err)
        if ok && st.Code() == codes.DeadlineExceeded {
            panic(err)
        }
        panic(err)
    }

 return err
}

服务端的超时

连接超时

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

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

服务实现中的超时

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

func (s *server) AddOrder(ctx context.Context, orderReq *pb.Order) (*wrapperspb.StringValue, error) {
 log.Printf("Order Added. ID : %v", orderReq.Id)

 select {
 case <-ctx.Done():
  return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
 default:
 }

 orders[orderReq.Id] = *orderReq

 return &wrapperspb.StringValue{Value: "Order Added: " + orderReq.Id}, nil
}

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

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

db.WithContext(ctx).Save(&users)

拦截器中的超时

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

func unaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
 defer cancel()

 // Invoking the handler to complete the normal execution of a unary RPC.
 m, err := handler(ctx, req)

 return m, err
}

超时传递

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

1b104b9c860a5c9484b026770c713bf6.png

如上的链路,如果当请求到达某一服务时,对于服务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还提供了超时传递的能力,让超时的请求不继续在链路中往下传递,提高链路整体的性能。


👇 欢迎关注👇

- END -

扫码关注公众号「网管叨bi叨」

给网管个星标,第一时间吸我的知识 👆

网管整理了一本《Go 开发参考书》收集了70多条开发实践。去公众号回复【gocookbook】领取!还有一本《k8s 入门实践》讲解了常用软件在K8s上的部署过程,公众号回复【k8s】即可领取!

觉得有用就点个在看  👇👇👇

<think>好的,用户现在让我介绍一下github.com/grpc-ecosystem/go-grpc-middleware这个包。首先,我需要回想一下之前用户的问题历史。之前他们询问了Gin框架的跨域处理,现在转到了gRPC的中间件包,可能是在构建微服务架构,需要了解如何增强gRPC的功能。 首先,用户可能正在使用gRPC进行服务间通信,但发现需要一些通用功能,比如日志记录、认证、重试机制等。这时候,他们需要中间件来统一处理这些横切关注点,而go-grpc-middleware正好提供了这样的解决方案。 接下来,我需要确定这个包的主要功能和用途。这个仓库是gRPC中间件的集合,支持拦截器链,允许开发者组合多个中间件。常见的中间件包括日志、认证、监控、限流、重试、超时等。用户可能希望了解它的核心功能、使用场景、如何集成到现有项目,以及具体的例子。 然后,我需要考虑用户可能的真实需求。他们可能对gRPC有一定的了解,但不太清楚如何高效地管理中间件,或者如何将多个中间件组合使用。可能他们遇到了代码重复的问题,或者需要统一处理一些通用逻辑,比如认证和日志记录,而不想在每个服务中重复编写代码。 此外,用户可能关心如何安装和使用这个包,是否有示例代码,以及在生产环境中需要注意什么。比如,中间件的顺序可能会影响功能,或者某些中间件需要特定的配置,如认证中间件需要与具体的认证逻辑结合。 我需要分结构来介绍这个包,先讲它的作用,再讲核心功能,比如支持的中间件类型、链式调用、与各种工具的集成。然后给出安装方法和使用示例,比如如何添加日志和认证中间件。接着,说明适用场景,比如微服务中的统一日志、API限流、重试机制等。 还要注意比较与其他中间件库的不同,可能这个包的优势在于社区支持和丰富的中间件集合。同时,注意事项部分需要强调中间件顺序的重要性,性能影响,以及生产环境的配置建议,比如启用监控和链路追踪。 最后,用户可能有更深层次的需求,比如如何自定义中间件,或者如何处理复杂的拦截器链,这些可能需要进一步解释或提供文档链接。不过根据当前问题,先覆盖基本介绍和常见使用场景,确保用户能快速上手并理解其价值。</think>--- `github.com/grpc-ecosystem/go-grpc-middleware` 是一个为 gRPC 服务提供**链式中间件**支持的 Go 语言库。它通过拦截器(Interceptor)机制,帮助开发者gRPC 请求处理过程中插入通用逻辑(如日志、认证、监控等),是构建企业级 gRPC 服务的核心工具之一。 --- ### **核心功能** | 功能 | 作用 | 典型中间件示例 | |-------------------|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------| | **链式拦截器** | 支持同时组合多个中间件(客户端和服务端均可使用) | `ChainUnaryServer(logger, auth, retry)` | | **常用中间件** | 提供开箱即用的中间件实现 | 日志、认证、重试、超时、限流、链路追踪、恢复(Recovery)等 | | **与生态集成** | 无缝对接 Prometheus、OpenTelemetry、Zap 等工具 | `grpc_prometheus.NewServerMetrics()` 用于 Prometheus 监控 | | **流式拦截支持** | 同时支持 Unary(一元)和 Streaming(流式)RPC 的拦截 | 流式请求的日志记录和超时控制 | --- ### **安装与基础使用** ```bash go get github.com/grpc-ecosystem/go-grpc-middleware/v2 ``` #### **服务端示例**(日志 + 认证) ```go import ( "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" ) func main() { // 创建中间件链 chain := grpc.ChainUnaryServer( logging.UnaryServerInterceptor(logger), // 日志中间件 auth.UnaryServerInterceptor(authFunc), // 认证中间件(需自定义 authFunc) ) // 启动 gRPC 服务 server := grpc.NewServer(grpc.UnaryInterceptor(chain)) pb.RegisterYourServiceServer(server, &service{}) server.Serve(lis) } ``` --- ### **典型应用场景** | 场景 | 实现方式 | |---------------------|--------------------------------------------------------------------------| | **统一请求日志** | 使用 `logging` 中间件记录 RPC 方法、耗时、状态码 | | **JWT 认证** | 通过 `auth` 中间件解析请求头中的 Token | | **自动重试** | 客户端使用 `retry` 中间件实现幂等请求的重试逻辑 | | **API 限流** | 结合 `ratelimit` 中间件限制每秒请求数 | | **错误监控** | 集成 Prometheus 中间件统计 RPC 调用次数和延迟 | --- ### **关键中间件列表** | 中间件类型 | 功能说明 | |------------------|--------------------------------------------------------------------------| | `logging` | 结构化日志记录(支持 Zap、Logrus 等日志库) | | `recovery` | 捕获 panic 防止服务崩溃 | | `validator` | 自动验证请求参数(需结合 protoc-gen-validate 使用) | | `ratelimit` | 基于令牌桶算法的限流控制 | | `retry` | 客户端自动重试(可配置重试条件和次数) | | `opentracing` | 分布式链路追踪(兼容 Jaeger、Zipkin 等) | --- ### **高级用法示例** #### **自定义中间件开发** ```go // 实现一个 Unary 拦截器(统计请求耗时) func metricsInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { start := time.Now() resp, err := handler(ctx, req) prometheus.ObserveLatency(info.FullMethod, time.Since(start)) return resp, err } ``` #### **流式拦截器组合** ```go // 流式 RPC 中间件链 streamChain := grpc.ChainStreamServer( logging.StreamServerInterceptor(logger), recovery.StreamServerInterceptor(), ) ``` --- ### **注意事项** 1. **中间件顺序** 中间件按添加顺序执行(类似洋葱模型),例如: ```go // 执行顺序:日志 → 认证 → 业务逻辑 → 认证 → 日志 grpc.ChainUnaryServer(loggingInterceptor, authInterceptor) ``` 2. **性能影响** 避免在拦截器中执行阻塞操作(如直接访问数据库),必要时使用异步处理。 3. **生产建议** - 启用 `recovery` 中间件防止服务崩溃 - 集成 Prometheus 监控关键指标 - 使用 `validator` 确保请求参数安全 --- 通过该库,开发者可以快速构建高可观测性、高可靠性的 gRPC 微服务,同时保持代码的简洁性。其模块化设计使得功能扩展和维护成本大幅降低。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值