记录一次 tokio 异步场景下 RPC 接口 timeout 的排查过程
记录一次 tokio 异步场景下 RPC 接口 timeout 的排查过程
在踩坑的过程中不断理解 tokio 的原理,本文已进行脱敏处理
问题现象
rpc server 是在 k8s pod 中运行的, 运行时主要包含以下 2 个模块:
- rpc 接口: 用户可以对其发起 rpc 请求获取实时数据
- 内部数据处理任务: 随着时间的推移,内部数据不断更新,确保用户通过 rpc 获取的数据是正确的 内部数据处理任务会涉及到 S3 的数据下载,以及一些 DB 的元数据更新,采用的是异步任务的设计,rpc 接口也是异步任务。
将这个服务部署到 k8s pod 后,本地循环访问 rpc 接口发现会概率性 timeout 超时
问题排查
在排查问题时,分为以下几个主要的步骤:
- 排查是不是 k8s 自身的网络问题导致的,发现虽然 traceroute 最后两跳有的时候会 timeout,但是 traceroute -I 会正常执行
- 在项目内启动一个新的 rpc 接口,该接口执行数字乘法,将用户请求的数据 x2 返回。并且暂停执行内部数据处理任务,观察 test rpc 是否还会 timeout
在新建 test rpc 后发现,rpc 的请求响应非常迅速,不会出现概率性的 timeout,这让我怀疑是因为后台的数据处理任务干扰了 rpc server 的运行。 起初我以为是内部的锁导致了 rpc server 的运行时等待,但是实际上 rpc timeout 的表现是 rpc server 根本没有收到任何网络请求,也就是说 rpc server 根本没有进行内部的数据搜索(不可能是因为内部等待锁导致的 timeout)。
后来分析了项目使用的 tokio,发现 rpc server 和内部数据处理任务均使用的是一个 tokio runtime,猜测在有限的资源条件下,这样非常容易发生 work-stealing,内部数据处理的异步任务会挤压 rpc server 的运行资源,从而导致 rpc server 无法响应客户端发送的请求。
在做好资源隔离之后 (rpc server 放到 1 个单独的线程内,并使用 tokio 创建新的 runtime) rpc server 再也没有出现过 timeout 不过还是需要好好的去考虑该怎么才能更加合理的设计 tokio 异步运行时的资源分配,rust 项目启动的时候因为 main 函数上面的注解,会自动分配一个异步运行时,也许可以把这个注解取消掉换成更加灵活的手动控制,这样就不用在异步运行时内再新建运行时了。
本文由作者按照 CC BY 4.0 进行授权