在實戰中使用nginx-rtmp遇到的TCP連線問題分析
在實戰中使用nginx-rtmp遇到的TCP連線問題分析
背景
前段時間公司做了一次體育賽事的現場直播,網路由某通訊公司負責搭建,主要測試5G CPE上行網路的頻寬和穩定性,為了做到萬無一失,他們同時搭建了一條用作備份的400M光纖線路。通過配置交換機來做到主備切換,要達到以下的效果:
- 無線鏈路down掉,交換機自動檢測到丟包,丟包到指定數量(可以在交換機中配置),自動切換到備用鏈路。
- 無線連結恢復,備用鏈路切換回無線鏈路。
參考 靜態路由與SLA技術
我們採用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, applicationDrop 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 連線斷連問題剖