libcurl
,一直也比较稳定,没有出什么问题,所以第一时间怀疑是 Go 服务 B 的问题。Go 服务其实也比较简单,是用
gin 框架
实现的 HTTP 协议代理,用来把业务请求解包后,再重新按照第三方的协议封包后转发到第三方。通过加日志等方法排除了业务逻辑部分代码的问题,初步怀疑是我们用 gin 的姿势不对。为了快速验证,我就用 gin 简单写了一个 go 的 server,用业务的 client 来请求 mock 的 server。
gist: mock_server.go
,核心部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
func MeshCall (meshPath string , c *gin.Context) { start := time.Now() uinStr := c.Query("uin" ) uin, err := strconv.ParseUint(uinStr, 10 , 64 ) if err != nil { c.Status(http.StatusBadRequest) return } body, _ := ioutil.ReadAll(c.Request.Body) log.Println("Request Body: " , string (body)) c.Status(http.StatusOK) c.Writer.Header().Add("code" , strconv.FormatInt(int64 (uin), 10 )) randomStr := StringWithCharset(1024 *1024 , charset) c.Writer.Write([]byte (randomStr)) log.Printf("Request processed in %s\n" , time.Since(start)) }
上面 mock 的 server 代码,包含了业务服务 B 里面的核心逻辑,比如用
ReadAll
来拿请求数据,用
c.Writer
来写回包内容。在 8089 启动服务后,不论是直接用命令行 curl 工具,还是用 C++ 的 client 去调用,都能正常得到 HTTP 回复。看来业务上 go 服务里 gin 的用法是没有问题,基本可以排除是 gin 自身的问题。替换 server 没发现问题,接下来替换下 client 看看?
gist: mock_client.go
,这里省略了 proto 部分,其中核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{ req, err: = http.NewRequestWithContext(context.Background(), http.MethodPost, "http://localhost:8089" , bytes.NewBuffer(serializedImageDataTwice)) if err != nil { log.Fatalf("failed to create request: %v" , err) } req.Header.Set("Content-Type" , "application/x-protobuf" ) client: = & http.Client {} resp, err: = client.Do(req) if err != nil { log.Fatalf("failed to send request: %v" , err) } defer resp.Body.Close() log.Printf("response header: %v" , resp.Header) log.Printf("response body: %v" , resp.Body) }
用这个 go client 请求前面 mock 的 go server,能正常解析回包。接着
去请求有问题的业务服务 B,发现不再超时了,能正常读到回包
。这下子有点懵了,梳理下前面的实验结果:
C++ Client A
Go Server B
特定请求必现超时
C++ Client A
Go Mock Server
Go Client
Go Server B
Go Client
Go Mock Server
只有
C++ Client A
和
Go Server B
在一起,特定请求才会超时
。已经没啥好思路来排查,只能尝试抓包,看看正常情况下和超时情况下 TCP/HTTP 包有啥区别。
Expect 100-continue
有一个详细的说明。
当使用 Post 或者 Put 方法,请求体超过一定大小(一般是 1024 字节)时,libcurl 自动添加”Expect: 100-continue”请求头
。对于上面的抓包中,请求的 body 中有一个比较大的图片,所以 C++ libcurl 的 client 请求中就多了这个 header。
现在只剩下一个问题了,
带有这个 header 为啥会导致请求超时呢
?libcurl 的文档中有提到下面一段:
Unfortunately, lots of servers in the world do not properly support the Expect: header or do not handle it correctly, so curl will only wait 1000 milliseconds for that first response before it will continue anyway.
可以看到,很多服务并没有很好支持
Expect: 100-continue
头部,不过 libcurl 也考虑了这种情况,在等待 1s 超时时间后,会继续发 body。从前面的抓包中也能验证这一点:
这里 libcurl client(IP最后是 253) 发送 header 后,没有收到服务端回复,所以等待了 1s 左右,开始继续发请求 body。正常情况下,服务器等收到完整响应后,再进行处理然后回包,最多也就浪费了 1s 的等待时间。不过这里我们的 server 表现比较奇特,在收到完整包后,先是回复了 100-continue,然后就没有任何反应了。导致 client 一直在等,直到 10s 超时。这又是什么原因导致的呢?