1. 程式人生 > >Mosquito的優化——epoll優化(七)

Mosquito的優化——epoll優化(七)

stat cep acc style 功能 pic 隊列 strong 使用epoll

本文由逍遙子撰寫,轉發請標註原址:

http://blog.csdn.net/houjixin/article/details/46413583

http://houjixin.blog.163.com/blog/static/3562841020155835146428/#


原版的mosquito在移動互聯網情況下,其性能不高。實際運營時一個mosquito實例能支持2萬連接就不錯了。mosquitto在網絡狀態不好的情況下,隨著用戶量的上升,其對cpu消耗將大幅添加,基本的CPU主要消耗在下面幾個方面:

(1)Poll機制的缺陷。

(2)Mosquitto內部訂閱樹機制的缺陷;

(3)其它消息發送,數據結構管理方面的缺陷;

本節將針對這些缺陷提出對應的優化策略和方法。

7.1、poll優化

7.1.1、優化原因

在mosquitto原始程序中,核心處理流程是對全部的socket進行監聽處理。該部分功能主要使用poll來完畢,可是它的效率較差,尤其當在線活動用戶較少的情況下,性能更差,這一定程序上影響了mosquitto性能,epoll效率的低下主要是因為以下3個原因:

1) poll在每次監聽port之前,都須要又一次註冊全部須要監聽的socket;

2) poll返回的結果中,僅僅會改動有事件發生的socket相應的poll結構體,因此,在使用時須要對全部註冊的socket相應的poll結構體進行掃描,才幹推斷出那些socket有事件發生。

3) 在內部實現上,poll須要查詢全部註冊的socket以確定其是否有事件發生。

Poll的上述問題是是其自身實現方式造成的,非常難進行優化。針對poll的這些問題,linux實現了一個更高效的實現方式:epoll,相對而言。epoll則不須要poll這些復雜操作,epoll具有下面3個長處:

1) epoll中,僅僅須要將被監聽的socket註冊進epoll一次。興許epoll就會監聽它,而不須要每次監聽之前又一次註冊。

2) epoll返回結果包括了全部有事件發生的socket,因此處理過程中,掃描全部這些有事件發生的socket就可以,而不須要掃描全部註冊的socket。

3) 在內部實現上,epoll不需查詢全部註冊的socket,它內部是:全部有事件發生的socket自己將自己掛載epoll的就緒隊列上,epoll僅僅需返回就緒隊列中的socket就可以。

因此,本次優化首先選擇對poll進行優化。主要使用epoll替換poll,以提升系統的運行效率。

7.1.2、優化方案:

Mosquitto中對poll的使用主要集中在文件loop.c的在函數mosquitto_main_loop中,因此本次改動將使用一個新的函數epoll_mosquitto_main_loop來取代它。另外,在用法上,epoll的監聽函數epoll_wait返回全部就緒socket,而mosquitto程序中須要知道socket相應的conetxt才幹完畢業務處理,因此,為了支持epoll,須要添加一個hash表t_fd2context來完畢socket到其相應conetxt的映射。

Mosquito的核心處理邏輯主要在函數mosquitto_main_loop中,該函數主要完畢了下面功能:

1) 更新系統topic的信息。

2) 將全部監聽socket放入poll結構體pollfds中。

3) 掃描全部context,完畢下列工作:

假設context有消息發送,則將消息依照mqtt協議發送出去;

檢查context是否超時;

將context的socket放入poll的結構體pollfds中。

4) 掃描全部conetxt,假設context中有消息,則更新消息的時間戳。

5) 調用poll。輪詢poll的結構體pollfds全部的socket;

6) 處理poll的結果,需完畢以下兩個工作:

掃描全部的context,查看其相應的socket是否有事件發生,假設有,則進行讀寫處理。

處理全部的監聽socket,假設有新的連接進入。則為之創建相應的context

7) 進行又一次載入配置文件等可選擇操作。

在poll的工作過程中。上述操作將會循環運行,使用epoll優化之後的mosquitto的核心流程將會有所改變,為:

1) 向epoll中註冊監聽port。該步驟在循環之前運行;以下2)之後的步驟將會放入循環運行。

2) 更新系統topic的信息。

3) 依據策略掃描所有context。完畢以下兩個工作:

回收context,並將該context的索引放入空暇索引數組中;

檢查超時,假設超時,則將該context與其socket的映射從hash表t_fd2context中刪除。

4) 調用epoll的epoll_wait函數獲取全部的就緒socket;

5) 對全部的就緒socket進行處理,主要完畢以下的工作:

假設就緒的socket是監聽接口,則對監聽接口進行處理。為每一個新進來的業務socket建立context,並將其註冊進epoll;

假設不是監聽socket,則從socket到cotext的hash表t_fd2context中找到該socket相應的context。然後完畢相應的處理,假設找不到。則斷開此socket的連接。

6) 進行又一次載入配置文件等可選擇操作。

因為epoll和poll的工作方式不同,因此須要對上述流程進行改動以使其能適應epoll的要求,主要改動之處包含:

1) Socket註冊方式;poll中每次循環都須要將socket又一次註冊入poll。而epoll僅僅須要開始註冊一次就可以;

2) 返回結果處理。poll中須要對所有註冊的socket結構體進行掃描,才幹推斷某個socket是否有數據處理;epoll直接返回就緒socket,因此無需所有遍歷註冊的socket結構體。

3) 添加socket到相應context的映射,因為epoll直接返回就緒socket,而mosquitto中須要找到該socket相應的context才幹進行上層應用的處理,因此須要添加一個hash表完畢socket到context的映射。

4) 改動消息發送部分的功能

7.1.3、詳細實現

Epoll的優化也採用原來的單線程結構,並使用一個大循環“while”完畢對任務的處理,該大循環被放在函數epoll_mosquitto_main_loop中,該函數與函數mosquitto_main_loop(該函數是使用poll時的主要業務處理)的參數全然同樣,並在函數mosquitto_main_loop中調用epoll_mosquitto_main_loop。因此程序中原來調用poll的主業務處理函數mosquitto_main_loop的地方將會被轉向調用epoll的主業務處理函數epoll_mosquitto_main_loop。從而實現對epoll功能的調用,系統的流程例如以下圖5-1所看到的。

技術分享

圖7-1 使用epoll之後的系統流程圖

1、socket註冊

epoll在使用時僅僅需將待監控的socket增加一次就可以。這一點與poll在用法上有區別。在mosquitto程序中,須要epoll監控的socket包含監聽socket和業務socket;Socket的註冊過程由函數reg_socket完畢,註冊socket的監聽類型為EPOLLIN,採用默認的水平觸發模式。

1) 監聽socket。此類型socket負責接收client的新連接。比如程序中默認的1883port相應的socket,client將使用該port連接到mosquitto。在函數epoll_mosquitto_main_loop的主循環開始直接之前完畢註冊,僅僅進行一次註冊,興許不再反復註冊。

2) 業務socket,該類型socket負責完畢client和mosquitto之間的業務數據的傳輸;業務socket將在新連接進入時完畢註冊,此過程在函數epoll_loop_handle_result中完畢。

2、Epoll的事件處理

Epoll事件處理將由函數epoll_loop_handle_result來完畢,在該函數中將循環掃描epoll返回的全部就緒socket。並對每一個就緒的socket進行處理,處理的方式為:

1) 假設為監聽socket,則首先調用mqtt3_socket_accept函數對新連接進來的socket進行處理,處理過程包含:為socket創建相應的context等。其次將socket註冊入epoll。

2) 假設監聽port為業務socket,則讀取該結構體上的數據。然後對數據進行處理。

技術分享

圖7-2 epoll事件處理流程

Mosquito的優化——epoll優化(七)