客户端、网关、服务各设一个超时会怎样?链路一长,deadline 是怎么一路传下去的?重试和超时叠在一起时谁说了算?这篇用 客户端 → A → B 的三层调用逐项实测,线上的 grpc-timeout 头也一并抓出来。
客户端超时 1000 ms A 调 B 的超时 无 → 客户端 成功,用时 503.5 ms;B 看到的 grpc-timeout 头 999514u;B 收到 1 次、做完 1、被取消 0 客户端超时 1000 ms A 调 B 的超时 300 ms → 客户端 code=DeadlineExceeded,用时 304.2 ms;B 看到的 grpc-timeout 头 299990u;B 收到 1 次、做完 0、被取消 1 客户端超时 200 ms A 调 B 的超时 1000 ms → 客户端 code=DeadlineExceeded,用时 202.1 ms;B 看到的 grpc-timeout 头 197163u;B 收到 1 次、做完 0、被取消 1 客户端超时 200 ms A 调 B 的超时 无 → 客户端 code=DeadlineExceeded,用时 200.7 ms;B 看到的 grpc-timeout 头 199184u;B 收到 1 次、做完 0、被取消 1 客户端超时 无 A 调 B 的超时 无 → 客户端 成功,用时 503.6 ms;B 看到的 grpc-timeout 头 (无);B 收到 1 次、做完 1、被取消 0
调用链上每一层的超时叠加起来,谁先到期谁说了算。内层的超时比外层剩余时间还长就没有作用。无论哪一层先到期,客户端看到的都是 DeadlineExceeded,分不清是自己的还是下游的,要靠日志、追踪或错误详情区分。
A 先耗 0 ms:客户端→A 的 grpc-timeout 999419u;A→B 的 grpc-timeout 999400u;B 看到的剩余 deadline 999 ms;成功,用时 13.1 ms A 先耗 200 ms:客户端→A 的 grpc-timeout 998313u;A→B 的 grpc-timeout 797916u;B 看到的剩余 deadline 797 ms;成功,用时 214.9 ms A 先耗 600 ms:客户端→A 的 grpc-timeout 999244u;A→B 的 grpc-timeout 397083u;B 看到的剩余 deadline 396 ms;成功,用时 614.4 ms 客户端不设超时:客户端→A 的 grpc-timeout:(无);A→B 的 grpc-timeout:(无);B 看到的剩余 deadline -1(-1 表示没有 deadline)
gRPC 在 grpc-timeout 头里传剩余时间,每一跳按当前时刻重新计算,前面耗掉的时间自然被扣掉。没设 deadline 就一路都没有。
客户端超时 1000 ms → A 实际尝试 3 次(B 收到 3 次、被取消 3);客户端 code=DeadlineExceeded,用时 1002.1 ms 客户端超时 2000 ms → A 实际尝试 3 次(B 收到 3 次、被取消 3);客户端 code=DeadlineExceeded,用时 1209.1 ms 客户端超时 无 → A 实际尝试 3 次(B 收到 3 次、被取消 3);客户端 code=DeadlineExceeded,用时 1206.2 ms
外层的 deadline 是总预算。重试的次数和每次的超时加起来超出时,最后一次会被截断,而每次失败的尝试下游都做了(部分)工作。
A 传 ctx 客户端 code=DeadlineExceeded,用时 202.1 ms;放弃后 100 ms:B 被取消 1 次;1.8 秒后:B 做完 0 次、被取消 1 次 A 用 Background 客户端 code=DeadlineExceeded,用时 202.1 ms;放弃后 100 ms:B 被取消 0 次;1.8 秒后:B 做完 1 次、被取消 0 次
转发下游调用必须用收到的 ctx(或它的派生),否则上游放弃之后,下游仍把活做完。