轉(zhuǎn)自 http://m.blog.csdn.net/article/details?id=52980399
TCP是一個復雜的協(xié)議雁社,每個機制在帶來優(yōu)勢的同時也會引入其他的問題。 Nagel算法和delay ack機制是減少發(fā)送端和接收端包量的兩個機制, 可以有效減少網(wǎng)絡包量项郊,避免擁塞膛檀。但是锰镀,在特定場景下, Nagel算法要求網(wǎng)絡中只有一個未確認的包咖刃, 而delay ack機制需要等待更多的數(shù)據(jù)包泳炉, 再發(fā)送ACK回包, 導致發(fā)送和接收端等待對方發(fā)送數(shù)據(jù)嚎杨, 造成死鎖花鹅, 只有當delay ack超時后才能解開死鎖,進而導致應用側(cè)對外的延時高枫浙。 其他文字已經(jīng)介紹了相關(guān)的機制刨肃, 已經(jīng)有一些文章介紹這種時延的場景古拴。本文結(jié)合具體的tcpdump包,分析觸發(fā)delay ack的場景真友,相關(guān)的內(nèi)核參數(shù)黄痪, 以及規(guī)避的方案。
背景
給redis加了一個proxy層盔然, 壓測的時候發(fā)現(xiàn)桅打, 對寫入命令,數(shù)據(jù)長度大于2k后愈案, 性能下降非常明顯挺尾, 只有直連redis-server的1/10. 而get請求影響并不是那么明顯。
分析
觀察系統(tǒng)的負載和網(wǎng)絡包量情況站绪, 都比較低潦嘶, 網(wǎng)絡包量也比較小, proxy內(nèi)部的耗時也比較短崇众。 無賴只能祭出tcpdump神奇掂僵, 果然有妖邪。
22號tcp請求包顷歌, 42ms后服務端才返回了ack锰蓬。 初步懷疑是網(wǎng)絡層的延時導致了耗時增加。Google和km上找資料眯漩, 大概的解釋是這樣: 由于客戶端打開了Nagel算法芹扭, 服務端未關(guān)閉延遲ack, 會導致延遲ack超時后赦抖,再發(fā)送ack舱卡,引起超時。
原理
Nagel算法队萤,轉(zhuǎn)自維基百科
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
簡單講轮锥, Nagel算法的規(guī)則是:
如果發(fā)送內(nèi)容大于1個MSS, 立即發(fā)送要尔;
如果之前沒有包未被確認舍杜, 立即發(fā)送;
如果之前有包未被確認赵辕, 緩存發(fā)送內(nèi)容既绩;
如果收到ack, 立即發(fā)送緩存的內(nèi)容还惠。
延遲ACK的源碼如下:net/ipv4/tcp_input.c
基本原理是:
如果收到的數(shù)據(jù)內(nèi)容大于一個MSS饲握, 發(fā)送ACK;
如果收到了接收窗口以外的數(shù)據(jù), 發(fā)送ACK救欧;
如果處于quick mode歪今, 發(fā)送ACK;
如果收到亂序的數(shù)據(jù)颜矿, 發(fā)送ACK寄猩;
其他, 延遲發(fā)送ACK
其他都比較明確骑疆, quick mode是怎么判斷的呢田篇? 繼續(xù)往下看代碼:
影響quick mode的一個因素是 ping pong的狀態(tài)。 Pingpong是一個狀態(tài)值箍铭, 用來標識當前tcp交互的狀態(tài)泊柬, 以預測是否是W-R-W-R-W-R這種交互式的通訊模式, 如果處于诈火, 可以用延遲ack兽赁, 利用Read的回包, 將Write的回包冷守, 捎帶給發(fā)送方刀崖。
如上圖所示, 默認pingpong = 0拍摇, 表示非交互式的亮钦, 服務端收到數(shù)據(jù)后, 立即返回ACK充活, 當服務端有數(shù)據(jù)響應時蜂莉,服務端將pingpong = 1, 以后的交互中混卵, 服務端不會立即返回ack映穗,而是等待有數(shù)據(jù)或者ACK超時后響應。
問題
按照前面的的原理分析幕随,應該每次都有ACK延遲的蚁滋,為什么我們測試小于2K的數(shù)據(jù)時, 性能并沒有受到影響呢合陵?
繼續(xù)分析tcpdump包:
按照Nagel算法和延遲ACK機制枢赔, 上面的交互如下圖所示, 由于每次發(fā)生的數(shù)據(jù)都包含了完整的請求拥知, 服務端處理完成后, 向客戶端返回命令響應時碎赢, 將請求的ACK捎帶給客戶端低剔,節(jié)約一次網(wǎng)絡包。
再分析2K的場景:
如下表所示, 第22個包發(fā)送的數(shù)據(jù)小于MSS襟齿, 同時姻锁,pingpong = 1, 被認為是交互模式猜欺, 期待通過捎帶ACK的方式來減少網(wǎng)絡的包量位隶。 但是, 服務端收到的數(shù)據(jù)开皿,并不是一個完整的包涧黄,不能產(chǎn)生一次應答。服務端只能在等待40ms超時后赋荆,發(fā)送ACK響應包笋妥。
同時,從客戶端來看窄潭,如果在發(fā)送一個包春宣, 也可以打破已收數(shù)據(jù) > MSS的限制。 但是嫉你,客戶端受Nagel算法的限制月帝, 一次只能有一個包未被確認,其他的數(shù)據(jù)只能被緩存起來幽污, 等待發(fā)送嫁赏。
觸發(fā)場景
一次tcp請求的數(shù)據(jù), 不能在服務端產(chǎn)生一次響應油挥,或者小于一個MSS
規(guī)避方案
只有同時客戶端打開Nagel算法潦蝇, 服務端打開tcp_delay_ack才會導致前面的死鎖狀態(tài)。 解決方案可以從TCP的兩端來入手深寥。
服務端:
關(guān)閉tcp_delay_ack, 這樣攘乒, 每個tcp請求包都會有一個ack及時響應, 不會出現(xiàn)延遲的情況惋鹅。 操作方式:
echo 1 > /proc/sys/net/ipv4/tcp_no_delay_ack
但是则酝, 每個tcp請求都返回一個ack包, 導致網(wǎng)絡包量的增加闰集,關(guān)閉tcp延遲確認后沽讹, 網(wǎng)絡包量大概增加了80%,在高峰期影響還是比較明顯武鲁。
設(shè)置TCP_QUICKACK屬性爽雄。 但是需要每次recv后再設(shè)置一次。 對應我們的場景不太適合沐鼠,需要修改服務端redis源碼挚瘟。
客戶端:
關(guān)閉nagel算法叹谁,即設(shè)置socket tcp_no_delay屬性。
staticvoid_set_tcp_nodelay(intfd) {
intenable =1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,(void*)&enable,sizeof(enable));
}
避免多次寫乘盖, 再讀取的場景焰檩, 合并成一個大包的寫;避免一次請求分成多個包發(fā)送订框, 最開始發(fā)送的包小于一個MSS析苫,對我們的場景, 把第22號包的1424個字節(jié)緩存起來穿扳, 大于一個MSS的時候衩侥,再發(fā)送出去, 服務端立即返回響應纵揍, 客戶端繼續(xù)發(fā)送后續(xù)的數(shù)據(jù)顿乒, 完成交互,避免時延泽谨。
參考資料:
http://jerrypeng.me/2013/08/mythical-40ms-delay-and-tcp-nodelay/
http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201231214038740/
http://blog.chinaunix.net/uid-28387257-id-3658980.html
https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_input.c