docker swarm mode 下容器重启IP引起的 CLOSE_WAIT 问题

问题

问题简述

以下图. server docker restart后, client端写入的日志丢失, 而且无报错.
由于不支持时序图, 把时序图代码嵌入在代码里.linux

​```sequence
client->server: log_data
client->server: log_data
server->server: docker restart
server->client: fin
client->server: log_data loss without error
​```

tcp state diagram

clipboard.png

问题定位过程

为何卡在CLOSE_WAIT.

看tcp状态转换图, 能够看到client收到了fin, 一直没有recv, 一直卡在CLOSE_WAIT. 和实际的代码是吻合的.
那么, 为何在server docker restart 引起CLOSE_WAIT后, client发消息仍然不报错呢?
由于:docker

  1. tcp协议容许client在收到fin后, 继续发送消息.
  2. server 在docker restart后 ip 改变, client仍是往原来的ip发送消息, 没有主机通知client rst, 致使消息在系统buffer里积压.

积压信息以下:segmentfault

root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0
tcp        1  402 10.0.0.186:62281        10.0.0.16:27017         CLOSE_WAIT  4308/server
root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0
tcp        1  70125 10.0.0.186:62281        10.0.0.16:27017         CLOSE_WAIT  4308/server

此时, 在elixir socket接口层面来看, 无论socket的状态, 仍是发送, 都是ok的.api

iex(client@client.)25> socket |> :inet.port
{:ok, 57395}
iex(client@client.)26> socket |> :gen_tcp.send("aaa")
:ok

若是主动close, 则会进入LAST_ACK状态socket

iex(client@client.)27> socket |> :gen_tcp.close()    
:ok
root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0
tcp        1  70126 10.0.0.186:62281        10.0.0.16:27017         LAST_ACK    -

CLOSE_WAIT的恢复

若是代码仍是只发不收. 是检测不到CLOSE_WAIT的. 显然, 应用层心跳是一个解决方案. 那么, 不使用心跳, 只发不收的状况下, 何时才能检测到错误呢?tcp

  1. send buffer 满
  2. tcp keepalive, 默认状况下须要2小时才能检测到链接错误. 见linux keepalive探测对应用层socket api的影响
相关文章
相关标签/搜索