天天看點

[原創]nginx寫日志時機與tcp write寫成功是否送達對端疑問解析

前些天和另外部門的同僚在排查一個網絡問題, 問到nginx日志中成功記錄了http 200響應碼能否證明響應資料就達到了對端? 這個問題涉及nginx在做server功能時寫日志是在什麼時機? 是client端收到響應資料後才生成, 還是nginx丢出資料就生成了而不管是否client端收到資料?

做上層應用的人員一般對底層網絡部分研究的較少,另外針對應用層調用write寫tcp資料并傳回寫入的位元組數就認為成功到達了對端這個問題很多人存在誤解。涉及的nginx發出響應處理流程和linux系統tcp的處理過程,,簡要解釋如下:

通過ngx_http_output_filter()發送響應資料給client

ngx_http_write_filter()會調用c->send_chain()往用戶端發送資料,c->send_chain()的取值

在不同作業系統,編譯選項以及協定下(https下用的是

ngx_ssl_send_chain)會取不同的函數,典型的linux作業系統下,它的取值為ngx_linux_sendfile_chain(),這個函數中使用rc = writev(c->fd, header.elts, header.nelts);發送資料;

響應的位元組全部發送完成後調用 ngx_http_finalize_request(r, rc);

ngx_http_finalize_request()

-->ngx_http_terminate_request()強制結束請求

或者直接調用-->

-->ngx_http_close_request()

-->ngx_http_free_request()

-->ngx_http_log_request(r);//記錄日志

這裡定義為資料拷貝到協定棧緩存和緩存資料發送兩個階段。

應用層發送資料時調用tcp資料發送函數可以是write、send、sendmsg 這三個函數參數中攜帶需要發送的資料,最終在核心層面都是通過調用__sock_sendmsg()實作,對tcp資料來說__sock_sendmsg()函數中會再調用tcp_sendmsg(); 而tcp_sendmsg()函數中根據現有的tcp緩存是否足夠會選擇将應用層的資料複制到sk_buff中,複制完成的sk_buff挂入到sk_write_queue 隊列尾部等待發送; 這個流程的代碼中用copied變量代表了真實從應用層已經拷貝到sk_buff中的資料量,下邊會走真正的tcp發送流程。但不管下邊的流程能否真正的發送出去資料,會傳回這個copied到應用層代表寫入成功的數量; 而協定棧緩存資料的發送就由我們所熟知的tcp可靠傳輸機制去保證到達對端了。

協定棧緩存資料的發送通過以下幾個函數負責:

-->tcp_push()

-->__tcp_push_pending_frames()

-->tcp_write_xmit()

** 在tcp_write_xmit()中會逐漸檢查本端的擁塞視窗(擁塞控制算法不斷的在調整)設定是否有配額cwnd_quota可以發送封包? 配額不足則跳出;

** 進行發送視窗snd_wnd 檢測, 若發送的mss超過了發送視窗則跳出;

** 若開啟了nagle算法不能立即發送此封包則跳出;

如果能通過以上等的限制則調用tcp_transmit_skb()真正的執行發送資料。

綜合所述tcp資料的發送其實是個異步的執行過程,應用層負責把資料寫入到tcp緩存中,而緩存中的資料需要靠tcp的可靠傳輸機制去保證發送到對端,由于tcp的可靠傳輸機制執行過程中會考慮擁塞視窗、對端接收視窗、mss、nagle算法等因素,執行完成後傳回的copied位元組量并不一定就代表對端接收成功了。

下一篇: ts學習筆記