【最佳实践】OSS开源工具ossutil-Cannot assign requested address解决方案

问题

某用户使用如下命令上传目录到OSS,中间隐去bucket name。

./ossutil cp oss://<xxxx>/img/330802010400071904 -r -j 15 /home/oss/vis-test/

出现如下错误。

【最佳实践】OSS开源工具ossutil-Cannot assign requested address解决方案

分析

原因

由于Linux分配的客户端连接端口用尽,无法建立socket连接所致,虽然socket正常关闭,但是端口不是立即释放,而是处于TIME_WAIT状态,默认等待60s后才释放。

由于客户端频繁的连服务器,由于每次连接都在很短的时间内结束,导致很多的TIME_WAIT,且用光了可用的端口号,所以新的连接没办法绑定端口,即"Cannot assign requested address"
是客户端的问题不是服务器端的问题。

通过netstat,可查看处于TIME_WAIT状态的连接。

sudo sysctl -a | grep tw
sudo netstat -antp | grep TIME_WAIT | wc -l

TCP状态图

为什么会出现这种情况?先看下TCP状态图。

【最佳实践】OSS开源工具ossutil-Cannot assign requested address解决方案

TCP的连接有11种状态。

- ESTABLISHED
  The socket has an established connection.
- SYN_SENT
  The socket is actively attempting to establish a connection.
- SYN_RECV
  A connection request has been received from the network.
- FIN_WAIT1
  The socket is closed, and the connection is shutting down.
- FIN_WAIT2
  Connection is closed, and the socket is waiting for a shutdown from the remote end.
- TIME_WAIT
  The socket is waiting after close to handle packets still in the network.
- CLOSE
  The socket is not being used.
- CLOSE_WAIT
  The remote end has shut down, waiting for the socket to close.
- LAST_ACK
  The remote end has shut down, and the socket is closed. Waiting for acknowledgement.
- LISTEN
  The socket is listening for incoming connections.
- CLOSING
  Both sockets are shut down but we still don't have all our data sent.

通过netstat命令可能还能看到UNKNOWN状态。

- UNKNOWN
  The state of the socket is unknown.

TCP规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态。

比如,

  • 当某个应用进程在CLOSED状态下执行主动打开时,TCP将发送一个SYN,且新的状态是SYN_SENT。
  • 如果这个TCP接着接收到一个带ACK的SYN,它将发送发送一个ACK,且新的状态是ESTABLISHED。这个状态是绝大多数数据传送发生的状态。

ESTABLISHED状态转移

  • 如果某个应用进程在接收到一个FIN之前调用close(即主动关闭),那就转换到FIN_WAIT_1状态。
  • 如果某个应用进程在ESTABLISHED状态期接收到一个FIN(即被动关闭),那就转到CLOSE_WAIT状态。

TIME_WAIT状态

TIME_WAIT状态又称为2MSL等待状态。

所谓MSL (Maximum Segment Lifetime)指的是报文段在网络内存活的最大时间。RFC 793建议的MSL为2分钟,具体的实现中一般为30秒或1分钟。

TIME_WAIT状态能够

  • 可靠的实现TCP全双工连接的终止
    当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留2MSL时间。
    这样可让TCP再次发送最后的ACK以防这个ACK丢失(如果丢失的话,另一端超时并重发最后的FIN)。
  • 允许老的重复分节在网络中消逝
    假设在192.168.1.254:1500和192.168.11.219:21端口之间有一个TCP连接。关闭这个连接,过一段时间后在相同的
    IP:port之间建立另外一个连接。
    后一个连接称为前一个连接的化身(incarnation),因为连接的IP:port相同,TCP必须防止某个连接的老的分组在该
    连接终止后再现,从而被误解成属于同一个连接的某个新的化身。

    为做到这一点,TCP将不给处于TIME_WAIT状态的连接发起新的化身。既然TIME_WAIT状态的持续时间是2MSL,这就足以
    让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒被丢弃。
    
    通过实施这一个规则,就能保证每成功建立一个TCP连接时,来自该连接先前化身的老的重复分组都已经在网络中消逝了。

再分析

  • 只有主动关闭的一方才会进入TIME_WAIT状态,被动关闭方不会。处在TIME-WAIT的状态下,需要等2MSL时间后,系统才会回收这条连接,端口才可以继续被使用。
  • 一般来说,都是客户端执行主动关闭,服务端都是被动关闭的。因为客户端重新连接时,一般会使用一个新的端口号(通常都是系统分配的),所以不会受TIME_WAIT的限制。
  • 为了防止TIME_WAIT对服务端的影响——服务端一般都使用的是固定的知名端口,如果重启的话(主动关闭),很可能因为TIME_WAIT的限制无法创建建立连接。所以socket API提供了SO_REUSEADDR选项,它让调用者可以对处于2MSL状态(即TIME_WAIT状态)的本地端口进行赋值。

方案

1. 修改系统TCP参数

修改运行ossutil 所在机器上的系统tcp参数。

- 调低端口释放后的等待时间,默认为60s,修改为30s或者15s
  sysctl -w net.ipv4.tcp_fin_timeout=30

- tcp_tw_resue, 默认为0,修改为1,释放TIME_WAIT端口给新连接使用
  sysctl -w net.ipv4.tcp_tw_reuse=1

- 支持TCP时间戳
  sysctl -w net.ipv4.tcp_timestamps=1

- 快速回收socket资源,默认为0,修改为1
  sysctl -w net.ipv4.tcp_tw_recycle=1

- 持久化
  sysctl -p

2. 增加可用端口

$ sysctl -a |grep port_range
net.ipv4.ip_local_port_range = 52768  60999     ====>52768~60999端口可用

修改该参数
$ vi /etc/sysctl.conf
net.ipv4.ip_local_port_range = 22768   60999    ====>22768~60999端口可用

Reference

上一篇:【比赛】NOIP2017 总结


下一篇:go-使用 unsafe 修改 struct 中的 field 的值