本來一篇文章就該搞定的。結果要分上下篇了。主要是最近頸椎很不舒服。同時還在做秒殺的需求也挺忙的。 現在不能久坐。看代碼的時間變少了。然後還買了兩本治療頸椎的書。在學着,不過感覺沒啥用。突然心裡好害怕。如果頸椎病越來越重。以後的路怎麼走。
現在上下班有跑步,然後坐一個小時就起來活動活動。然後在跟着同時們一起去打羽毛球吧。也快30的人了。現在發覺身體才是真的。其他都沒什麼意思。兄弟們也得注意~~
廢話不多說。下面介紹下netio。
netio在系統中主要是一個分包的作用。netio本事沒有任何的業務處理。拿到包以後進行簡單的處理。再根據請求的指令字發送到對應的業務處理程序去。
2.1 多程序下是監聽socket的方式
1)比如我們想建立三個程序同時處理一個端口下到來的請求。
2)父程序先建立socket。然後再listen。注意這個時候父程序frok。 2個程序出來。加上父程序就是3個程序
3)每個程序單獨建立位元組epoll_create和epoll_wait. 并把socket放到epoll_wait裡
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuE2M0MDO1ITMhVjN0YWYzEzMhVjMlFjY2ATY3YmM1QjMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
以上是平台多程序下監聽同一個端口的方式。我們下面探究下為什麼要這麼做
2.1.1、為什麼要fork出來的子程序來繼承父程序的socket。而不是多個程序綁定同一個端口?
首先如果一個端口正在被使用,無論是TIME_WAIT、CLOSE_WAIT、還是ESTABLISHED狀态。 這個端口都不能被複用,這裡面自然也是包括不能被用來LISTEN(監聽)。是以在三個程序裡分别listen和bind端口肯定會失敗的
2.1.2 、為什麼不能使用SO_REUSEADDR來是多個程序監聽同一個端口?
首先我們來看下SO_REUSEADDR的用途
服務端重新開機的時候會進入到TIME_WAIT的狀态。這個時候我們在bind的端口會失敗的。但是我們不可能等TIME_WAIT狀态過去在重新開機服務 因為TIME_WAIT可能會在一分鐘以上。這個時候我們設定為SO_REUSEADDR就是使得端口處在TIME_WAIT時候,可以複用監聽。
注意SO_REUSEADDR隻是在TIME_WAIT 重新開機服務的時候有用。如果你是多個程序要bind同一個端口。且IP相同。那麼即使你設定了SO_REUSEADDR也會失敗
因為SO_REUSEADDR允許在同一端口上啟動同一伺服器的多個執行個體,隻要每個執行個體捆綁一個不同的本地IP位址即可。對于TCP,我們根本不可能啟動捆綁相同IP位址和相同端口号的多個伺服器。
2.1.3、TIME_WAIT的作用。為什麼要TIME_WAIT的?
因為TCP實作必須可靠地終止連接配接的兩個方向(全雙工關閉),
一方必須進入 TIME_WAIT 狀态,因為可能面臨重發最終ACK的情形。
否則的會發送RST
2.1.4、多進行下實作監聽同一個端口的原因
因為建立子程序的時候,複制了一份socket資源給子程序。其實可以這麼了解。其實隻有父程序一個socket綁定了端口。其他子程序隻是使用的是複制的socket資源
2.1.5、epoll放到fork之後會怎麼樣?
netio起5個程序
我們先做幾個實驗然後再具體分析
a)實驗一
正常請求。我們慢慢的按順序發送十個請求。每個請求都是新的請求。
我們看下netio處理的程序pid 發現每次都是2358
開始我以為會這五個請求來競争來取socket接收到的請求化。那麼每次處理的請求的子程序應該不一樣才對。但是每次都是同一個請求
b)實驗二
我們先并發2個請求。看服務處理程序pid還是2358
這個時候我們用戶端fork8個程序并發請求服務。發現2357和2358開始交替處理
c)實驗三
我們在epoll_wait後面.recv之前 加上sleep(100000)
然後發送一個請求。發現每個程序被喚醒以後 但是因為sleep阻塞了。 然後會接着喚醒别的程序來處理。每次喚醒都會被阻塞。一直到5個程序全部被阻塞
d)實驗四
我們在epoll_wait後面recv 後面 加上是sleep(100000)
然後發送一個請求。發現有一個程序recv處理完以後。sleep住。其他程序并沒有被喚醒。
<code>after epoll_wait pid:2358</code>
當我們并發兩個請求的時候。發現喚醒了兩個程序
四個實驗已經做完現在我們來具體分析下原因
1)實驗一中為什麼 每次隻有一個程序來處理。
首先我們三個程序都epoll_wait同一個fd。按理來說這個時候其實應該喚醒三個程序都來處理的。但是每次都隻有一個程序來處理。如果是程序競争處理的話。别的子程序應該也有機會來處理的。但是沒有。這就是我們所謂的“驚群”現象。但是并沒有發生。
查了下資料發現。核心采用的不是競争。而是配置設定政策。在有多個子程序epoll_wait 同一個fd的時候。會選擇一個子程序來處理這個消息。 并不會喚醒其他子程序。
2)實驗二中 并發增大的時候 為什麼會開始有多個子程序來處理。
其實這裡做的很有意思。核心輪流喚醒監聽fd的子程序。如果子程序很快處理完。那麼就一直讓這個子程序來處理fd.但是如果子程序處理不完。速度沒那麼快。會接着喚醒别的子程序來處理這個fd.
即fd事件到達的時候。核心會先去看下A程序是否繁忙。如果不繁忙 。則讓這個A程序一直處理。如果發現A程序繁忙中。會去檢視程序B是否繁忙。如果不繁忙則B處理 後面的子程序以此類推
是以我們看到 并發請求增大的時候 開始有多個子程序來處理了
3)實驗三、5個程序為什麼都被喚醒了?
其實就是上面說的。被sleep住了。我們認為程序就是繁忙狀态中。會依次通知其他程序來處理。
4)實驗四 為什麼隻有一個程序被喚醒處理
我們在sleep放在recv之後。發現隻有一個程序被喚醒。 我們可以任務程序已經接受并處理了任務。所有不需要再通知其他程序了
這裡我們小結下:
epoll放在fork之後這種方式不會引起驚群現象。 會輪詢選擇其中一個子程序來處理。如果子程序來不及處理。則會通知另外一個子程序來處理。
但是以上結論是做實驗和查資料得來的。并沒有看核心源碼。所有如果有看過核心源碼的同學。希望能指點下。
2.1.6、epoll放到fork之前會怎麼樣?
把epoll放到fork 之前。當發送一個請求的時候 發現也是隻喚醒了一個程序來處理。
這裡其實跟epoll放到fork之後是一樣的。
但這裡有個很蛋痛的地方 。
我們系統用到了unix域來做消息通知。當container處理完消息。會發送uinx域來通知neito來處理回包。
但是回包的時候。不知道為什麼 5個程序都被喚醒了來被處理。最後有三個程序被主動結束了。
下面是我自己的了解
a)
首先
是程序id為
6000
的netio處理的資料。
當container回包實際資料隻會通知
f000_6000的uinx域中
如下圖。有5個程序。就有5個uinx域
b)
但是由于多程序公用一個epoll。其他程序也被喚醒了。然後判斷發現這個fd是uxin域的類型。
然後就會去不停的讀取自己對應的unix域檔案。
但是其實沒有消息的。container隻回到了
f000_6000
中是以其他程序一直recvfrom =-1
而且由于正在處理
的程序不夠及時。這個消息沒處理。epoll的特性是會一直通知程序來處理。是以其他程序會一直讀自己的unix域。然後就一直recvfrom =-1
如下圖。沒貼全。除了程序6000其他程序列印了一堆這樣的資訊
c)
最後我們讀程序6000從的uinx域中讀到資料後。
其他程序剛好這個時候拿到fd是被處理過的。這個時候再來處理這個fd就是未定義的。
而我們對未定義的fd會直接stop程序。是以最後三個程序被主動關閉了
這裡我們小結一下
因為沒有看核心代碼 所有對這種情況隻有靠實驗和猜了。。。。。跪求大神指導
1、首先針對TCP端口核心應該是做了特殊處理。是以epoll在fork前還是後。如果處理及時。應該都是隻有一個程序被喚醒。來處理。處理不及時會依次喚醒别的程序。并不會造成驚群現象(就是那種臨界資源多個程序來搶這個包。最後隻有一個程序能搶到包。但是做實驗發現好像并不是競争的關系)
2、但是針對unix域的fd。公用一個epoll沒有特殊處理。就會造成驚群現象。并且多個程序都能拿到這個fd來處理。
先看下圖。netio定時器所處在的位置。
由于epoll_wait了10毫秒。無論是否有請求觸發。
每隔10毫秒都會輪詢一次。這樣可以防止當container通知netio的時候。消息丢失而導緻netio不能處理的情況
定時器是一個比較重要的概念。每個服務程序都會有個定時器來處理定時任務。這裡介紹下netio的定時器. 這裡檢查定時時間事件做兩件事。一個是找出已經到達的時間事件。并執行子類的具體處理函數。第二個是給自動時間事件續期。
netio初始化的時候。會注冊一個60秒的循環時間事件。即每60秒會執行一次時間事件。這個時間事件有以下幾個動作。1)清除逾時的socket 2)查詢本地指令字清單 3)定時輸出netio的統計資訊
3.1 定時器的資料結構
netio的定時器資料結構是最小堆。即最近的定時任務是在最小堆上
a)申請了65個大小的二維數組
這裡為什麼申請1+64.其實隻有64個可用。
其中的一個指針是用來輔助最小堆算法的。
即m_pNodeHeap[0] = NULL; 是一直指向空的。
最小堆的最小值 是m_pNodeHeap【1】.
b) CNode成員變量的意義
3.2 定時清理無效連接配接
a)首先如果有用戶端connet進來。
netio會把這個connet的fd儲存下來 還有到來的時間。
m_mapTcpHandle[iTcpHandle] = (int)time(NULL);
b ) 每次接受到資料 都會更新這個時間
c ) netio有個配置檔案。 我們一般是設定為10秒。每次檢查定時事件的時候。都會去檢查m_mapTcpHandle。
用目前時間 跟 m_mapTcpHandle裡面儲存的時間比較。當發現超過10秒的時候
我們認為這個連接配接時無效的。然後會把這個fd關閉。并删除
d) 是以如果 要保持長連接配接的話。 需要用戶端不停的發送心跳包。來更新這個時間
3.3 定時統計netio的統計資訊
這有個很有意思的地方。
在初始化的時候。我們已經注冊了一個每60秒執行一次輸出的netio狀态的時間事件
前面我們說了m_dwCount這個值用來控制自動時間事件的次數。我們每一分鐘會輸出netio的狀态資訊。
這就是固定的時間事件。我們入參dwCount設定0.那麼系統就認為你這個時間事件是需要無限循環的。
就設定了一個最大值 (unsigned int )-1。
這個值算出來是4294967295。
我們以每分鐘一次來計算。差不多要8000多年才能把這個m_dwCount減為0~~~~~
當輸出完狀态資訊後。會把netio的一些狀态初始化為0.因為我們的狀态輸出是統計一分鐘的狀态。比如這一分鐘的請求包個數。比率。丢包個數等。
3.4 定時請求指令字
這裡還是比較重要的是。比如我們新加了一個服務。如果不重新開機netio是不知道的。但是我們會沒隔一分鐘去請求所有的指令字。 可以發現有心的服務加了進來。
4.1、 netio_debug.log日志分析
下面分析下簡單netio的日志。這裡就大概介紹了 。不具體在介紹netio的值了 。 多看看就知道是什麼意思的。
還是請求道container. container發現參數校驗不對直接把包丢回給netio的回包隊列
如下圖
a)192.168.254.128:58638 當請求到來的時候會列印請求的IP和端口
b)Handle = 00700008 但是這個socket不是原生的。是經過處理的。
c)ConnNum = 1 目前有多個連接配接數
d) Timestamp = 1460881087 請求到了的時間戳
a) SendMsgq REQUEST START 這裡是把内容丢到消息隊列裡
b) _NotifyNext REQUEST START 這裡是通知container的uinx域。有消息丢到了你的消息隊列裡
c) OnRecvFrom REQUEST START 這裡是接收到了container發來的uinx域 。告訴netio我已經處理完。丢到你的回包消息隊列裡去了。你趕緊去處理吧
d) OnEventFire request:0 從消息隊列裡拿到資料并開始處理。 回給用戶端包
e)OnClose REQUEST START 用戶端發送close socket信号。 服務端接收後。關閉socket
4.2 netio_perform.log 日志分析
以下都是一份鐘的統計資料
PkgRecv 收到的包
PkgSent 發送出去的包
ErrPkgSent 錯誤的包。
PkgPushFail 這個暫時沒用到
PkgSendFail 這個是netio 包發送的時候 。發不出去的個數
BytesRecv 收到的位元組數
BytesSent 發送出去的位元組數
MaxConn 最大連接配接數。這個值不是一份内的最大值。是從開始到輸出統計是。最高的同時連接配接資料
TcpConnTimeout 因為逾時。netio自動關閉的TCP連接配接。
Cmd[0x20630001] 是netio從回包隊列中拿到。指令字
Count[15] 該指令字一分鐘内總共拿到的回包總數
AverageTime[0] 每個包的平均處理時間。 這裡是拿這15個包從netio-container-netio這期間的總時間 除以 15得到的平均時間 機關是毫秒
MaxTime[1] 這15個包中耗時最長的一個包。所耗時間
AverageRspLen[89] 平均每個包回給用戶端的位元組數
MaxRspLen[89] 最大的一個回包位元組數
Ratio[100] 這裡先會拿到一個一分鐘内netio接受包的總個數 (這裡指用戶端來的請求包) 。然後用用0x20630001指令字的個數來除以總包數再乘以100。得到0x20630001在這一分鐘内。所占處理包的比重。
後面接着的一串是 指令字0x20630001的分布在不同相應時間的個數
最後一天是正對一分鐘所有指令字包的統計
1)對大型系統。統計日志很重要。可以時事了解系統的狀态
2)一定要處理好多程序的關系
3)最後 一定要保護好身體。 身體才是根本啊~~~