问题描述
当我们微服务闲置一段时间后,目前有A 微服务监听 B微服务。当B服务重启后,A服务中的B服务地址并没有随之变更。我们使用的是 consul作为服务注册发现中心。
排查过程
由于我们设置了日志打印debug级别,因此当B服务重启后,A服务一定会打印 [resolver] update instances xxx 信息的,但目前我们没有看到该信息。所以我们就从其更新的入口来作为切入点。
- 所在文件 contrib/registry/consul/v2/registry.go
- 所在函数 func (r *Registry) resolve(ctx context.Context, ss *serviceSet) error
- 我们可以看到有以下这个逻辑来监听名字服务的变化
- 接着就是关键的了,按理说,这个goroutine 应该正常运行,那么服务变化才会实时获取到,所以出问题肯定是要闷获取服务列表又问题,要么就是ctx.Done导致这个goroutine退出了,从而不再更新服务了。
- 既然我们已经有两个方向可以查询了,那么就可以开始一个一个来看。
- 首先是服务列表更新失败请求,这种情况只需要打印下每次请求结果就行,如果说是因为consul版本问题导致了更新失败这个应该很容易重现出来。
- 那么另一种情况就是ctx 被cancel了,导致for循环的退出,这个怎么发现呢?这里就需要我们断电return函数,然后一直等待测试。其实该过程中我们踩了很多坑,譬如不断重启下游服务,不断发送grpc请求。一直没有复现,直到我们静静地就挂着,过了大概半小时就出现了。
- 结论:这个东西是由于grpc idle timeout触发了这个机制,我们通过代码跟中这个ctx,就会发现这个是被封装到discoveryResolver中的,而这个Resolver是被grpc的 ccResolverWrapper 触发的。如下
ccr.serializer.Schedule(func(context.Context) {
if ccr.resolver == nil {
return
}
ccr.resolver.Close()
ccr.resolver = nil
})
当idle timeout 时候就会触发这个ccr.resolver.Close(),从而导致discoveryResolver 的close函数被触发,因此cancel就随即执行,后续的服务发现函数就自动退出了。
处理方式
既然这个是由于grpc idle机制触发,因此我们可以通过配置该超时机制来临时解决这个问题
kgrpc.WithOptions(grpc.WithIdleTimeout(24*time.Hour))
根本解决方式当然是靠 kratos官方来处理。但目前来看他们还没有把这个issue 合并,因此我们也只能先自行处理了。
issue地址 issue
好就说到这里,喜欢的点个关注哈。。。。。