介绍两个经典的网络问题,
问题1: 访问位于Azure Application Gateway之后的nodejs server, 偶尔会触发502
问题2: 请求一个Azure App Service, 如果在230s之内请求没有返回,必定timeout
问题1分析
首先来分析问题1,初步看502,那么很自然的认为是后台服务down了,但是检查server状态很正常,并且测试反馈只是偶尔某个请求502,再次刷新立马就恢复正常。由于重现概率很低,尝试去抓网络包,没抓到有用的信息,只能进行猜测。出现502既然不是server的状态有问题,那么就是server端即upstream意外的close了tcp connection。这种情况可能有多种原因比如Server端的资源紧张,比如Server端的keep-alive设置,已经检查过服务器状态正常,那么可能是这个keep-alive的设置导致。
什么是HTTP keep-alive
HTTP Keep-Alive,也称为HTTP persistent connection(持久连接),它允许在一个TCP连接上发送和接收多个HTTP请求和响应,而不需要为每一个新的请求/响应对重新建立和关闭连接。在HTTP/1.0中,默认不使用Keep-Alive,每一次请求/响应结束后,连接就会被关闭。为了启用Keep-Alive功能,客户端必须在请求头中包含Connection: keep-alive。在HTTP/1.1中,Keep-Alive是默认启用的,但是客户端和服务器都可以通过发送一个Connection: close的消息头来请求关闭连接。
那么默认的nodejs keepalive是多长时间,查阅官方资料,为5s。https://nodejs.org/docs/latest/api/http.html#serverkeepAliveTimeout
写段代码简单的试验下:
1 | const express = require('express'); |
查看response header:
抓取网络包并分析:
从图中可以看到,正常情况下,当收到最后一个ACK 5s之后, 由Server端(port 3000)会发送FIN Flag,开始关闭连接。这证明了Server端会主动的close connection, 而某些情况下会发生如下情况:
这个是网上截取的图,但是情况类似,某些情况,TCP Conenction没有正常的断开连接,而是直接Reset, 此时就会发生502。
解决方案
那么解决方案也很明了,只要Server端保证比Client端也就是Application Gateway的Keep-Alive 长,Server端不会在Client端认为可用的时候发送消息,就不会发生此类问题,查阅官方资料,Azure Applicaiton Gateway的HttpAlive v1为120s, v2为75s, 我们用的是V2, 设置为了120s > 75s, 此问题改动之后不再发生。但是需要声明的是用的相同的配置和代码的另外一个产品则从来没发生过此类问题。网络世界很复杂,也许不是这个原因,咱们这里只是拿这个问题来重点介绍HTTP Keep-Alive。
问题2分析
问题2的描述是在230s之内请求没有返回, 那么也就是说230s之内TCP connection一直没有流量传输,那么大概率是触发了TCP Idle Timeout。
什么是TCP Keep-Alive和TCP Idle Timeout
TCP Idle Timeout 是指TCP连接在无数据交换时可以保持空闲状态的最大时间。在这段时间后,如果没有任何数据包(例如TCP段)在连接上发送,连接就可能被认为是不再需要的,并且会被网络设备或操作系统自动关闭。这样做的目的是为了回收不再使用的资源,避免无用连接。
TCP Keep-Alive 是一种网络协议的机制,设计用于在TCP(传输控制协议)连接中检测对方是否已停止响应。TCP 是一种面向连接的协议,用于在计算机网络中的应用程序之间可靠地传输数据。在长时间的空闲期间,一个端点可能在没有任何通知的情况下不再可用,例如电脑可能已经崩溃或网络故障。Keep-Alive 机制可以帮助检测这些情况,确保连接仍然是活跃的,或允许应用程序在连接被对方关闭时采取适当的行动。
注意网络设备和应用程序可以调整TCP Keep-Alive的参数比如时间间隔和重试次数
注意不要把TCP Keep-Alive和Http Keep-Alive混淆。
在Azure中, Azure Load Balancer有个默认的4分钟的TCP Idle timeout, 这也就导致了如果app service一直没返回,则client端大概在230s(windows是230s, linux是240s)抛出timeout异常。
解决方案
那解决方案也比较清晰,由于Azure Load Balancer的设置在AppService里的服务中是不可配置的,那么只能改为异步方式,对于请求来说,可以先返回一个Id,然后循环查询Id对应的状态,而不是一直等待。