背景
前段时间公司做了一次体育赛事的现场直播, 网络由某通信公司负责搭建, 主要测试 5G CPE 上行网络的带宽和稳定性, 为了做到万无一失, 他们同时搭建了一条用作备份的 400M 光纤线路. 通过配置交换机来做到主备切换, 要达到以下的效果:
无线链路 down 掉, 交换机自动检测到丢包, 丢包到指定数量(可以在交换机中配置), 自动切换到备用链路.
无线链接恢复, 备用链路切换回无线链路.
参考 静态路由与 SLA 技术 https://blog.51cto.com/zxteach/1919615
我们采用 nginx-rtmp 搭建了 2 层 CDN.
测试
推流端推送 RTMP 流会向 nginx-rtmp 发送请求建立 TCP 链接, 推流过程中, 把交换机上的无线链路网线拔掉. 自动切换到光纤线路, 推流端重连后依然不能够成功建立链接, 推流软件卡死.
server 端的 TCP 链接一直存在:
- root@iz2zehy7gff0ksipgb4ch3z /u/l/nginx# netstat -natp | grep "1936"
- tcp 0 0 0.0.0.0:1936 0.0.0.0:* LISTEN 9467/nginx: master
- tcp 0 0 192.168.199.6:1936 223.71.3.82:46012 ESTABLISHED 11177/nginx: worker
nginx 报错了:
2019/05/20 15:44:58 [error] 6947#0: *286 live: already publishing, client: 223.71.3.82, server: 0.0.0.0:1936
此时
就是因为无线链接断开时, TCP 链接不能够被正常关闭, publisher 会一直存在导致的.
复习一下四次挥手:
我们知道 TCP 连接有一个特性:
TCP 连接一旦建立, 只要通信双方之间的中间结点 (包括网关和交换机, 路由器等网络设备) 工作正常, 那么在通信双方中的任何一方主动关闭连接之前, TCP 连接都将被一直保持下去. TCP 连接的这种特性, 使得一个长期不交换任何信息的空闲连接可以长期保持数小时, 数天甚至数月. 中间路由器可以崩溃, 重启, 网线可以被挂断再连通, 只要两端的主机没有被重启, TCP 连接就可以被一直保持下来.
可以看到, 网线虽然断掉了, 但是 server 端没有收到 client 的任何消息, server 端不会主动发起挥手, 因此连接会一直维持很长一段时间(我的测试机器上大概数小时). 链接断开后 server 端一直在发送 PSH+ACK:
如何才能实现快速重连
为源站加 load balance
加一个备源和一个调度服务, 调度策略采取轮询, 两次连续的 TCP 连接请求会被定向到不同的源站上面. 这个方法治标不治本, 切一次可以, 如果无线链路恢复, 再切回来的时候, 可能 TCP 链接还没有关闭.
添加 drop_idle_publisher
- Syntax: drop_idle_publisher timeout
- Context: rtmp, server, application
Drop publisher connection which has been idle (no audio/video data) within specified time. Default is off. Note this only works when connection is in publish mode (after sending publish command).
drop_idle_publisher 10s;
nginx-rtmp 会在指定的时间内丢弃空闲的 publisher:
- root@iz2zehy7gff0ksipgb4ch3z /u/l/n/logs# netstat -natp | grep "1936"
- tcp 0 0 0.0.0.0:1936 0.0.0.0:* LISTEN 11421/nginx: master
- tcp 0 0 192.168.199.6:1936 61.148.243.150:9338 ESTABLISHED 12923/nginx: worker
- tcp 0 1 192.168.199.6:1936 223.71.3.82:47240 FIN_WAIT1 -
可见这次是 server 端在 2s 后探测到这个 TCP 连接处于空闲状态, 主动发起了挥手消息, 此时 publisher 就被释放掉了, 再次推流会重新建立新的 TCP, 重新生成此 publisher.
上图是链路断掉后, TCP 链接完全断开前 server 端向 client 发送的数据包, 可以看到一直在发送 FIN + 最后一个数据包的 ACK, 时间间隔大概为 0.2 秒 ->0.4 秒 ->0.8 秒 ->1.6 秒 ->3.2 秒 ->6.4 秒 ->12.8 秒 ->25.6 秒
这种方法是可行的.
- so_keepalive
- listen
- syntax: listen (addr[:port]|port|unix:path) [bind] [ipv6only=on|off] [so_keepalive=on|off|keepidle:keepintvl:keepcnt|proxy_protocol]
- context: server
- Adds listening socket to NGINX for accepting RTMP connections
关于 TCP 探活机制的几个参数的说明:
keepcnt 关闭一个非活跃连接之前进行探测的最大次数 t
keepidle 对一个连接进行有效性探测之前运行的最大非活跃时间间隔
keepintvl 两个探测的时间间隔
设置如下参数:
listen 1936 so_keepalive=5s:2:2;
可以看到, 最后一个 ACK 没有回复后隔了 5 秒开始 TCP keep-alive 探活, 总共两次, 间隔 2 秒, 最后发送 RST+ACK 断开了 TCP 连接 .
参考
nginx-rtmp-module wiki
TCP 连接断连问题剖析
来源: https://www.cnblogs.com/harlanc/p/10896015.html