一次超时事件的排查

背景

生产环境中,调用公司封装的kms服务进行解密,偶报超时错误。但是看接口实际耗时只有100多ms。

一开始怀疑是errgroup中的ctx用错了,导致cancel掉了请求。后面确认以后发现不是。

最后查看基础框架封装的源码,默认的请求超时时间被设置成了30ms,真是气死。

当时有两个模块,一个是模块是对端接口,gateway会在ctx里面设置deadline,然后向下传递,这个模块没报错,因为ctx里面有deadline了,就没有用client设置的timeout
    if _, ok := ctx.Deadline(); ok {
        return next
    }

另一个模块是消费kafka回调函数里面的ctx,这个ctx里面没有设置deadline,所以会用client的timeout

grpc-go如何实现超时

通过下面的代码,可以看到grpc-go是通过context实现超时控制的。

import (
    "context"
    "time"

    pb "example.com/example.protobuf"
    "google.golang.org/grpc"
)

func main() {
    // 假设已经设置了连接和客户端
    conn, _ := grpc.Dial("your_grpc_server_address", grpc.WithInsecure()) // 使用实际的连接参数替换
    client := pb.NewYourServiceClient(conn)

    // 设置超时
    timeout := 3 * time.Second
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()

    // 使用带超时的 context 进行 gRPC 调用
    req := &pb.YourRequest{} // 使用实际的请求结构体替换
    resp, err := client.YourMethod(ctx, req)
    if err != nil {
        // 处理错误,可能是超时导致的
    }
    // 如果调用成功,处理响应
}

基础框架如何把timeout参数和grpc-go的超时机制整合

通过grpc的拦截器【只需要几个拦截器,把拦截器做成自定义hook的形式,方便添加更多的业务逻辑】,在发送请求前,读取请求里面的timeout参数,通过context.WithTimeout注进context来实现超时控制。

通过ctx注进去,然后请求前拿出来设置上去

func WithClientConfig(ctx context.Context, conf settings.ClientConfig) context.Context {
    return context.WithValue(ctx, clientConfigKey{}, conf)
}

设置timeout

func withTimeout(ctx context.Context, next func(context.Context) error) func(context.Context) error {
        //如果是stream类的rpc,则不许呀设置超时
    if rpc.IsStream(ctx) {
        return next
    }

        //如果context自己设置了超时,就不读配置里面的timeout参数去设置context了
    if _, ok := ctx.Deadline(); ok {
        return next
    }

    var timeout int64

    conf := grpcclient.GetClientConfig(ctx)

    timeout = conf.Timeout
    if timeout <= 0 {
        timeout = defaultTimeout
    }

    return func(ctx context.Context) error {
        ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Millisecond)
        defer cancel()
        return next(ctx)
    }
}

kms客户端缓存优化

kms客户端可以设置两个缓存,一个是加密的缓存,一个是解密的缓存。

根据uid后2位取模,设置加密的缓存,缓存过期时间为1小时。相同uid后缀的消息,在1小时以内都用相同的密钥加密。过期以后,重新去获取密钥,后面1小时的,又用另外一个密钥加密。

加密获取密钥对以后,把解密的密钥缓存下来,过期时间设置为24小时。这样,新发的消息,在24小时内被拉取,都不需要请求kms去解密,走缓存即可。

效果:加密密钥小时级别变化,解密大概率走缓存,性能好。即保证了安全性,又保证了性能。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 为什么需要超时控制? 很多连锁故障的场景下的一个常见问题是服务器正在消耗大量资源处理那些早已经超过客户端截止时间的...
    kevwan阅读 872评论 0 5
  • 1、事件还原 昨天下午,收到一个504的告警,显然这是一个超时告警。当时由于手头有其他事情,没在意,就只是瞄了一眼...
    Jackie_Zheng阅读 1,089评论 0 3
  • 原创文章,转载请务必将下面这段话置于文章开头处(保留超链接)。本文转发自[董泽润 blog],不允许修改题目及内容...
    董泽润阅读 4,977评论 1 7
  • 在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的。本文介绍了Go语言中如何定...
    雪上霜阅读 289评论 0 0
  • 大家好,我是杜欢,很荣幸能代表滴滴来做分享。我来滴滴的第一件事情就是帮助公司统一技术栈,在服务端我们要把以前拿 P...
    中v中阅读 1,805评论 0 25

友情链接更多精彩内容