Connectionrefud间歇性出现的问题定位
出现Connection refud的问题原因⼀般有三种:
1. 服务器的端⼝没有打开这种直接就是⼀直会Connection refud,不会间歇出现,可以直接排除;
2. 服务器的防⽕墙没有开⽩名单很多跟外部对接的时候,是需要将公司出⼝ip加到对⽅防⽕墙⽩名单,这种也会直接Connection refud,不会间歇出现,可以直接排除;
3. 服务器上的backlog设置的太⼩,导致连接队列满了,服务器可能会报Connection refud,或者Connecttion ret by peer,这个看服务器上的连接队列满时的设置;
详细的异常堆栈信息如下:
看报错⽅法:
是个native⽅法,毫不意外。因为是跟第三⽅云服务商对接,只能让他们查服务器配置的backlog⼤⼩(最后通过将backlog从50调到了4096),这⾥回顾⼀下tcp三次握⼿的过程。
正常的发起请求的三次握⼿如下:
大学生调查问卷
实现英文第⼀步:client 发送syn到rver发起握⼿;
第⼆步: rver收到syn后回复syn + ack 给client;
第三步:client收到syn + ack后,回复rver⼀个ack表⽰收到rver的syn + ack;
Tcp连接详细状态如下图:
1. 服务端调⽤bind() & listen() 函数后,会监听本地某个端⼝,例如8080;黄龙玉原石
2. 客户端发SYN,服务端收到,连接状态变为SYN_RCVD,将连接放到半连接队列syns queue中,同时回复syn+ack给client;
3. 客户端收到syn + ack,回复ack,客户端连接状态变为ESTABLISHED,服务器接收到客户端的ack,先看accept queue是否已满,如果没有满,将连接放到全连接队列,如果满了的话,有⼆种处理⽅式:
根据服务端tcp_abort_on_overflow的配置决定,如果配置为0,会丢弃客户端的ack, 过段时间重发syn + ack,也就是三次握⼿的第⼆步(如果客户端超时时间设置的太短,就容易引发Connection refud),如果配置为1,会直接返回RET,客户端的表⽰就是Connection ret by peer。
其中半连接队列的⼤⼩看: max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)
上⾯是我机器上半连接的配置,挺⼤的,26万。
全连接队列的⼤⼩: min(backlog, somaxconn), backlog是在socket创建的时候传⼊的,somaxconn是⼀个os级别的系统参数,不同操作系统不⼀样。
代码涉及到Socket这⼀层的操作时,需要⾃⼰传backlog的⼤⼩,否则默认值是50.
public ServerSocket(int port, int backlog) throws IOException {
清炖鱼怎么做法this(port, backlog, null);
}
所有上⾯Connection Refud很容易因为backlog设置的太⼩⽽发⽣,例如,nginx的配置就有backlog, 默认是511,Tomcat 默认是100。
⼀般来说,如果是公司⾃⼰的服务器,可以通过TCP建连接的时候全连接队列(accept队列)满了,通过⼀些命令可以查询队列情况:
netstat -s 命令
通过netstat -s | egrep "listen" 看队列的溢出统计数据,多执⾏⼏次,看全连接队列overflow次数有没有增长:
ss 命令
上⾯看Send-Q的值就是listen端⼝上全连接队列的最⼤值,Recv-Q就是当前全连接队列⽤了多少。
netstat跟ss命令⼀样也能看到Send-Q、Recv-Q这些状态信息,不过如果这个连接不是Listen状态的话,Recv-Q就是指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的 bytes;⽽ Send 则是发送队列中没有被远程主机确认的 bytes 数。
因此如果出现间歇性Connection Refud,检查是否有设置backlog, backlog设置的是否过⼩。
压⼒测试实践:
服务端代码:
public class BaSocketServer {
private ServerSocket rver;
private Socket socket;
private int port;
private InputStream inputStream;
private static final int MAX_BUFFER_SIZE = 1024;
public int getPort() {
return port;
}
public void tPort(int port) {
this.port = port;
}
public BaSocketServer(int port) {
this.port = port;
}
public void runServerMulti() throws IOException {
//故意设置backlog就只有10
this.rver = new ServerSocket(this.port, 10);
System.out.println("ba socket rver started.");
ExecutorService executorService = wFixedThreadPool(10);
while(true){
// the code will block here till the request come.
this.socket = rver.accept();
Runnable run = () ->{
InputStream inputStream = null;
try {
inputStream = InputStream();
byte[] readBytes = new byte[1024];
int msgLen;
StringBuilder stringBuilder = new StringBuilder();
while ((msgLen = ad(readBytes)) != -1) {
stringBuilder.append(new String(readBytes,0, msgLen,"UTF-8"));
}
System.out.println("get message from client: " + stringBuilder);
}catch (Exception ex){
ex.printStackTrace();
勇敢的我}finally {
try {
Thread.sleep(10000);
inputStream.clo();
socket.clo();
} catch (Exception e) {
e.printStackTrace();
}
}
};
四二拍指挥手势
executorService.submit(run);
}
//rver.clo();
}
public static void main(String[] args) {
BaSocketServer bs = new BaSocketServer(9799);
try {
bs.runServerMulti();
}catch (IOException e) {
e.printStackTrace();
}
}
}
backlog设置的只有10,这样让客户端连接的时候可以快速队列满。
客户端代码如下:
public class BaSocketClient {
private String rverHost;
private int rverPort;
private Socket socket;
private OutputStream outputStream;
public BaSocketClient(String host, int port) {
this.rverHost = host;
this.rverPort = port;
}
public void connetServer() throws IOException {
this.socket = new Socket(this.rverHost, this.rverPort);
this.outputStream = OutputStream();
// why the output stream?
}
public void ndSingle(String message) throws IOException {
try {
this.outputStream.Bytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
System.out.Message());
}
this.outputStream.clo();
this.socket.clo();
}
public static void main(String[] args) {
ExecutorService executorService = wFixedThreadPool(100);
京东订单查询Runnable run = () ->{
BaSocketClient bc = new BaSocketClient("127.0.0.1",9799);
try {物理删除
bc.ndSingle(String.format("%s, 你好, 我是吉⽶", Thread.currentThread().getName())); }catch (IOException e) {
e.printStackTrace();
}
};
for(int i = 0; i < 28; i++){
executorService.submit(run);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然后ss 可以看到全队列overflow次数马上爆了。