问题形容
业务反馈Golang服务在解析申请参数的时候,偶现呈现"EOF"谬误,狐疑网关或者两头链路失落了HTTP申请体,业务谬误日志统计如下:
阐明一下,Golang服务基于gin框架,解析POST申请参数形式如下:
func Handle(c *gin.Context) { err:= c.ShouldBindJSON(&req) //呈现err io.EOF}
HTTP申请没有body时候,就会呈现这种谬误。屡次确认,客户端日志显示申请都带有body,而且依据traceid查问某个异样申请的客户端日志,也显示带有body。
难道真是两头节点丢了申请体?可是不应该啊,网关(Nginx)在转发申请的时候,不可能失落body啊,而且客户端申请都带有"Content-Length",如果网关没有收到申请body,校验HTTP申请不残缺,也会间接返回400谬误啊。
查问网关access日志,显示request-body的确为空,阐明网关接管到的申请的确没有body。
须要阐明一下,从客户端到Golang服务,整个拜访链路为:client ——> ECDN ——> LVS ——> 网关Nginx ——> Golang服务
LVS只是四层负载平衡,也不会是它的问题。腾讯云ECDN,有可能,须要找服务方帮忙排查下。
ECDN排查
业务日志查问出异样申请,提供申请url,申请工夫,客户端IP给ECDN服务方,查问ECDN日志。结果显示,所有申请都是带有body,即便存在回源失败的状况,重试的时候也都带有body。
红框中的两个数字,第一个是head-length,第二个是body-length。
网关日志只能看到request-body为空,以及request_length,然而申请头以及申请体长度是看不到的。
然而发现,失常申请时候,网关日志request_length = ECDN日志head-length + body-length;异样申请时候,网关日志request_length = ECDN日志head-length。大概率ECDN的确没有带body。
另外确认,ECDN针对客户端的携带的header "Content-Length",也会转发给源站。网关节点批改日志格局,增加字段Content-Length;察看一段时间,申请失常时候Content-Length也是失常的,申请出错的时候Content-Length=0。
根本能够确认,ECDN转发过去的申请的确没有携带body,以及Content-Length=0。
查找多个异样case,发现出错的时候,ECDN都存在出错重试状况。在ECDN批改配置,去掉重试之后,EOF谬误再也没有了。
经ECDN服务方排查确认,重试逻辑存在bug,重试的确没有带body。
持续摸索
为什么第一次申请会失败呢?ECDN服务方给出线索,失败状况日志显示的谬误是"SSL Alert Close Notify。查问理解到,这谬误是HTTPS在建设加密链接的时候,源站SSL_shutdown被动敞开链接导致。
源站为什么会被动敞开链接呢?排查问题的时候,腾讯云ECDN方还进行了线上抓包,给出了局部抓包数据:
因为是HTTPS加密数据,抓包并不能看到具体的数据,wireshark导入网站密钥之后,发现仍然不能解密。最初才发现,加密算法采纳的是ECDHE,wireshark不反对此类密文的解密。
不过还是能够看到,第21号包返回的应该就是所谓的SSL Alert Close Notify,前面就是链接的FIN敞开了。
再次全局剖析一下,第一次申请在绝对0时刻收回,第二次申请在绝对时刻120秒收回。120秒如同有点相熟,查看网关Nginx配置,发现:
http{ keepalive_timeout 120;}
长连贯keepalive_timeout配置刚好是120秒,即120秒之内没有申请的话,Nginx(这里就是源站)会被动断开链接。
Nginx keepalive解决
解决实现以后申请时候,如果是长连贯Nginx会增加定时器,超时工夫刚好为keepalive_timeout,超时之后,被动敞开以后长链接。
static void ngx_http_set_keepalive(ngx_http_request_t *r){ //超时后处理办法 rev->handler = ngx_http_keepalive_handler; ngx_add_timer(rev, clcf->keepalive_timeout);}static void ngx_http_keepalive_handler(ngx_event_t *rev){ if (rev->timedout || c->close) { ngx_http_close_connection(c); return; }}voidngx_http_close_connection(ngx_connection_t *c){#if (NGX_HTTP_SSL) if (c->ssl) { if (ngx_ssl_shutdown(c) == NGX_AGAIN) { c->ssl->handler = ngx_http_close_connection; return; } }#endif}
Nginx有两个配置能够影响源站被动敞开链接(都归属与ngx_http_core_module):
//期待多长时间内,还没有申请达到,敞开链接Syntax: keepalive_timeout timeout [header_timeout];Default: keepalive_timeout 75s;Context: http, server, location//接管多少次申请后敞开链接Syntax: keepalive_requests number;Default: keepalive_requests 1000;Context: http, server, locationThis directive appeared in version 0.8.0.
同样的Nginx ngx_http_upstream_module在代理转发的时候,也反对相似的配置(留神默认配置,以及配置引入的版本):
Syntax: keepalive_timeout timeout;Default: keepalive_timeout 60s;Context: upstreamThis directive appeared in version 1.15.3.Syntax: keepalive_requests number;Default: keepalive_requests 1000;Context: upstreamThis directive appeared in version 1.15.3.
显然只有ECDN配置的keepalive_timeout以及keepalive_requests小于源站网关的配置即可,这样ECDN就会被动敞开长连贯。
不过貌似ECDN并不是基于Nginx实现的,曾经沟通,倡议实现相似的配置能力。