mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現三

y123456yzzyz發表於2020-10-28

transport_layer 網路傳輸層模組原始碼實現三

關於作者

前滴滴出行技術專家,現任OPPO 文件資料庫 mongodb 負責人,負責 oppo 千萬級峰值 TPS/ 十萬億級資料量文件資料庫 mongodb 研發和運維工作,一直專注於分散式快取、高效能服務端、資料庫、中介軟體等相關研發。 Github 賬號地址 :

1. 說明

   在之前的 <<Mongodb 網路傳輸處理原始碼實現及效能調優 - 體驗核心效能極致設計 >> << transport_layer 網路傳輸層模組原始碼實現 >> 一文中分析瞭如何閱讀百萬級大工程原始碼、 Asio 網路庫實現、執行緒模型、 transport_layer 套接字處理及傳輸層管理子模組、 session 會話子模組、 Ticket 資料收發子模組、 service_entry_point 服務入口點子模組。

本文將繼續分析網路傳輸層模組中service_state_machine 狀態機排程子模組核心原始碼實現。

請提前閱讀系列的前兩篇文章:

mongodb 核心原始碼實現、效能調優、最佳運維實踐系列 - 網路傳輸層模組原始碼實現一

mongodb 核心原始碼實現、效能調優、最佳運維實踐系列 - 網路傳輸層模組原始碼實現二

2. service_state_machine 狀態機排程子模組

service_state_machine 狀態機處理模組主要複雜一次完整請求的狀態轉換,確保請求可以按照指定的流程順利進行,最終實現客戶端的正常 mongodb 訪問。該模組核心程式碼實現主要由以下三個原始碼檔案實現( test 為測試相關,可以忽略):

2.1 核心程式碼實現

service_entry_point 服務入口點子模組分析中,當接收到一個新的連結後,在 ServiceEntryPointImpl::startSession(...) 回撥函式中會構造一個 ServiceStateMachine   ssm 類,從而實現了新連結、 session ssm 的一一對映關係。其中, ServiceStateMachine  類實現對ThreadGuard( 執行緒守護 ) 有較多的依賴,因此本文從這兩個類核心程式碼實現來分析整個狀態機排程模組的內部設計細節。

2.1.1 ThreadGuard 執行緒守護類核心程式碼實現

ThreadGuard 也就是 執行緒守護 類,該類主要用於工作執行緒名的管理維護、ssm 歸屬管理、該 ssm 對應 session 連結的回收處理等。該類核心成員及介面實現如下:

1. class ServiceStateMachine::ThreadGuard {  
2.     ......  
3. public:  
4.     // create a ThreadGuard which will take ownership of the SSM in this thread.  
5.     //ThreadGuard初始化,標記ssm所有權屬於本執行緒  
6.     explicit ThreadGuard(ServiceStateMachine* ssm) : _ssm{ssm} {  
7.         //獲取ssm狀態機所有權  
8.         auto owned = _ssm->_owned.compareAndSwap(Ownership::kUnowned, Ownership::kOwned);  
9.         //如果是一個連結一個執行緒模型,則條件滿足  
10.         if (owned == Ownership::kStatic) {   
11.         //一個連結一個執行緒模型,由於連結始終由同一個執行緒處理,因此該連結對應的ssm始終歸屬於同一個執行緒  
12.             dassert(haveClient());  
13.             dassert(Client::getCurrent() == _ssm->_dbClientPtr);  
14.             //標識歸屬權已確定  
15.             _haveTakenOwnership = true;  
16.             return;  
17.         }  
18.          ......
19.         //adaptive 動態執行緒模式走下面的模式  
20.   
21.         //把當前執行緒的執行緒名零時儲存起來  
22.         auto oldThreadName = getThreadName();   
23.         //ssm儲存的執行緒名和當前執行緒名不同  
24.         if (oldThreadName != _ssm->_threadName) {  
25.             //即將修改執行緒名了,把修改前的執行緒名儲存到_oldThreadName  
26.             _ssm->_oldThreadName = getThreadName().toString();  
27.            //把執行本ssm狀態機的執行緒名改為conn-x執行緒  
28.             setThreadName(_ssm->_threadName); //把當前執行緒改名為_threadName  
29.         }  
30.   
31.         //設定本執行緒對應client資訊,一個連結對應一個client,標識本client當前歸屬於本執行緒處理  
32.         Client::setCurrent(std::move(_ssm->_dbClient));  
33.          //本狀態機ssm所有權有了,歸屬於執行本ssm的執行緒  
34.         _haveTakenOwnership = true;  
35.     }  
36.     ......  
37.     //重新賦值  
38.     ThreadGuard& operator=(ThreadGuard&& other) {  
39.         if (this != &other) {  
40.             _ssm = other._ssm;  
41.             _haveTakenOwnership = other._haveTakenOwnership;  
42.             //原來的other所有權失效  
43.             other._haveTakenOwnership = false;  
44.         }  
45.     //返回  
46.         return *this;  
47.     };  
48.   
49.     //解構函式  
50.     ~ThreadGuard() {  
51.     //ssm所有權已確定,則析構的時候,呼叫release處理,恢復執行緒原有執行緒名  
52.         if (_haveTakenOwnership)  
53.             release();  
54.     }  
55.   
56.     //一個連結一個執行緒模型,標記_owned為kStatic,也就是執行緒名永遠不會改變  
57.     void markStaticOwnership() {  
58.         dassert(static_cast<bool>(*this));  
59.         _ssm->_owned.store(Ownership::kStatic);  
60.     }  
61.   
62.     //恢復原有執行緒名,同時把client資訊從排程執行緒歸還給狀態機  
63.     void release() {  
64.          auto owned = _ssm->_owned.load();  
65.          //adaptive非同步執行緒池模式滿足if條件,表示SSM固定歸屬於某個執行緒  
66.         if (owned != Ownership::kStatic) {  
67.         //本執行緒擁有currentClient資訊,於是把它歸還給SSM狀態機  
68.             if (haveClient()) {  
69.                 _ssm->_dbClient = Client::releaseCurrent();  
70.             }  
71.              //恢復到以前的執行緒名  
72.             if (!_ssm->_oldThreadName.empty()) {  
73.                 //恢復到老執行緒名  
74.                 setThreadName(_ssm->_oldThreadName);   
75.             }  
76.         }  
77.         //狀態機狀態進入end,則呼叫對應回收hook處理  
78.         if (_ssm->state() == State::Ended) {  
79.             //連結關閉的回收處理 ServiceStateMachine::setCleanupHook  
80.             auto cleanupHook = std::move(_ssm->_cleanupHook);  
81.             if (cleanupHook)  
82.                 cleanupHook();  
83.             return;  
84.         }  
85.   
86.         //歸屬權失效  
87.         _haveTakenOwnership = false;  
88.         //歸屬狀態變為未知  
89.         if (owned == Ownership::kOwned) {  
90.             _ssm->_owned.store(Ownership::kUnowned);  
91.         }  
92.     }  
93.   
94. private:  
95.     //本執行緒守護當前對應的ssm  
96.     ServiceStateMachine* _ssm;  
97.     //預設false,標識該狀態機ssm不歸屬於任何執行緒  
98.     bool _haveTakenOwnership = false;  
99. }

從上面的程式碼分析可以看出執行緒守護類作用比較明確,就是守護當前執行緒的歸屬狀態,並記錄狀態機ssm 不同狀態變化前後的執行緒名。此外,狀態機 ssm 對應的 session 連結如果進入 end 狀態,則該連結的資源回收釋放也由該類完成。

檢視mongod 或者 mongos 例項,如果啟動例項的時候配置了 serviceExecutor: adaptive 會發現這些程式下面有很多執行緒名為 conn-x worker-x 執行緒,同時同一個執行緒執行緒名可能發生改變,這個過程就是由ThreadGuard 執行緒守護類來實現。 synchronous 一個連結一個執行緒模型只有 conn-x 執行緒,adaptive 執行緒模型將同時存在有執行緒名為 conn-x worker-x 的執行緒,具體原理講在後面繼續分析,如下圖:

說明: synchronous 執行緒模式對應 worker 初始執行緒名為 conn-x adaptive 執行緒模型對應 worker 初始執行緒名為 worker-x

ThreadGuard 執行緒守護類和狀態機 ssm(service_state_machine) 關聯緊密,客戶端請求處理的內部 ssm 狀態轉換也和該類密切關聯,請看後續分析。

2.1.2 ServiceStateMachine  類核心程式碼實現

service_state_machine 狀態機處理模組核心程式碼實現透過 ServiceStateMachine 類完成,該類的核心結構成員和函式介面如下:

1. //一個新連結對應一個ServiceStateMachine儲存到ServiceEntryPointImpl._sessions中  
2. class ServiceStateMachine : public std::enable_shared_from_this<ServiceStateMachine> {  
3.    ......  
4. public:  
5.    ......  
6.    static std::shared_ptr<ServiceStateMachine> create(...);  
7.    ServiceStateMachine(...);  
8.    //狀態機所屬狀態
9.    enum class State {  
10.        //ServiceStateMachine::ServiceStateMachine建構函式初始狀態  
11.        Created,        
12.        //ServiceStateMachine::_runNextInGuard開始進入接收網路資料狀態  
13.        //標記本次客戶端請求已完成(已經傳送DB獲取的資料給客戶端),等待排程進行該連結的  
14.        //下一輪請求,該狀態對應處理流程為_sourceMessage  
15.        Source,         
16.        //等待獲取客戶端的資料  
17.        SourceWait,     
18.        //接收到一個完整mongodb報文後進入該狀態  
19.        Process,        
20.        //等待資料傳送成功  
21.        SinkWait,       
22.        //接收或者傳送資料異常、連結關閉,則進入該狀態  _cleanupSession  
23.        EndSession,     
24.        //session回收處理進入該狀態  
25.        Ended           
26.    };  
27.    //所有權狀態,主要用來判斷是否需要在狀態轉換中跟新執行緒名,只對動態執行緒模型生效  
28.    enum class Ownership {   
29.        //該狀態表示本狀態機SSM處於非活躍狀態  
30.        kUnowned,    
31.        //該狀態標識本狀態機SSM歸屬於某個工作worker執行緒,處於活躍排程執行狀態  
32.        kOwned,   
33.        //表示SSM固定歸屬於某個執行緒  
34.        kStatic   
35.    };  
36.      
37.    ......  
38. private:  
39.    //ThreadGuard可以理解為執行緒守護,後面在ThreadGuard類中單獨說明  
40.    class ThreadGuard;  
41.    friend class ThreadGuard;  
42.      
43.    ......  
44.    //獲取session資訊  
45.    const transport::SessionHandle& _session()  
46.    //以下兩個介面為任務task排程相關介面  
47.    void _scheduleNextWithGuard(...);  
48.    void _runNextInGuard(ThreadGuard guard);  
49.    //接收到一個完整mongodb報文後的處理  
50.    inline void _processMessage(ThreadGuard guard);  
51.    //以下四個介面完成底層資料讀寫及其對應回撥處理  
52.    void _sourceCallback(Status status);  
53.    void _sinkCallback(Status status);  
54.    void _sourceMessage(ThreadGuard guard);  
55.    void _sinkMessage(ThreadGuard guard, Message toSink);  
56.      
57.    //一次客戶端請求,當前mongodb服務端所處的狀態  
58.    AtomicWord<State> _state{State::Created};  
59.    //服務入口,ServiceEntryPointMongod ServiceEntryPointMongos mongod及mongos入口點  
60.    ServiceEntryPoint* _sep;  
61.    //synchronous及adaptive模式,也就是執行緒模型是一個連結一個執行緒還是動態執行緒池  
62.    transport::Mode _transportMode;  
63.    //ServiceContextMongoD(mongod)或者ServiceContextNoop(mongos)服務上下文  
64.    ServiceContext* const _serviceContext;  
65.    //也就是本ssm對應的session資訊,預設對應ASIOSession   
66.    transport::SessionHandle _sessionHandle;   
67.    //根據session構造對應client資訊,ServiceStateMachine::ServiceStateMachine賦值  
68.    //也就是本次請求對應的客戶端資訊  
69.    ServiceContext::UniqueClient _dbClient;  
70.    //指向上面的_dbClient  
71.    const Client* _dbClientPtr;  
72.     //該SSM當前處理執行緒的執行緒名,因為adaptive執行緒模型一次請求中的不同狀態會修改執行緒名
73.    const std::string _threadName;  
74.    //修改執行緒名之前的執行緒名稱  
75.    std::string _oldThreadName;  
76.    //ServiceEntryPointImpl::startSession->ServiceStateMachine::setCleanupHook中設定賦值  
77.    //session連結回收處理  
78.    stdx::function<void()> _cleanupHook;  
79.    //接收處理的message資訊  一個完整的報文就記錄在該msg中  
80.    Message _inMessage;   
81.    //預設初始化kUnowned,標識本SSM狀態機處於非活躍狀態,  
82.    //主要用來判斷是否需要在狀態轉換中跟新執行緒名,只對動態執行緒模型生效  
83.    AtomicWord<Ownership> _owned{Ownership::kUnowned};  
84. }

該類核心成員功能說明如下表:

  我們知道,連結、session SSM 狀態機一一對應,他們也擁有對應的歸屬權,這裡的歸屬權指的就是當前 SSM 歸屬於那個執行緒,也就是當前 SSM 狀態機排程模組由那個執行緒實現。歸屬權透過 Ownership 類標記,該類保護如下幾種狀態,每種狀態功能說明如下:

  Mongodb 服務端接收到客戶端請求後的資料接收、協議解析、從 db 層獲取資料、傳送資料給客戶端都是透過 SSM 狀態機進行有序的狀態轉換處理, SSM 排程處理過程中保護多個狀態,每個狀態對應一個狀態碼,具體狀態碼及其功能說明如下表所示:

以上是SSM 處理請求過程的狀態碼資訊,狀態轉換的具體實現過程請參考後面的核心程式碼分析。 listerner 執行緒接收到新的客戶端連結後會呼叫透過 service_entry_point 服務入口點子模組的 ssm->start() 介面進入 SSM 狀態機排程模組,該介面相關的原始碼實現如下:

1. //ServiceEntryPointImpl::startSession中執行  啟動SSM狀態機  
2. void ServiceStateMachine::start(Ownership ownershipModel) {  
3.     //直接呼叫_scheduleNextWithGuard介面  
4.     _scheduleNextWithGuard(   
5.     //listener執行緒暫時性的變為conn執行緒名,在_scheduleNextWithGuard中任  
6.     //務入隊完成後,在下面的_scheduleNextWithGuard呼叫guard.release()恢復listener執行緒名  
7.         ThreadGuard(this), transport::ServiceExecutor::kEmptyFlags, ownershipModel);  
8. }  
9.   
10. void ServiceStateMachine::_scheduleNextWithGuard(...) {  
11.     //該任務func實際上由worker執行緒執行,worker執行緒從asio庫的全域性佇列獲取任務排程執行  
12.     auto func = [ ssm = shared_from_this(), ownershipModel ] {  
13.         //構造ThreadGuard  
14.         ThreadGuard guard(ssm.get());    
15.         //說明是sync mode,即一個連結一個執行緒模式, 歸屬權明確,屬於指定執行緒  
16.         if (ownershipModel == Ownership::kStatic)   
17.             guard.markStaticOwnership();  
18.         //對應:ServiceStateMachine::_runNextInGuard,在這裡面完成狀態排程轉換  
19.         ssm->_runNextInGuard(std::move(guard));    
20.     };  
21.     //恢復之前的執行緒名,如果該SSM進入Ended狀態,則開始資源回收處理  
22.     guard.release();  
23.     //ServiceExecutorAdaptive::schedule(adaptive)   ServiceExecutorSynchronous::schedule(synchronous)  
24.     //第一次進入該函式的時候在這裡面建立新執行緒,不是第一次則把task任務入隊排程  
25.     Status status = _serviceContext->getServiceExecutor()->schedule(std::move(func), flags);  
26.     if (status.isOK()) {  
27.         return;  
28.     }  
29.     //異常處理  
30.     ......  
31. }

ServiceStateMachine::start() 介面呼叫 ServiceStateMachine::_scheduleNextWithGuard( ) 來啟動狀態機執行。 _scheduleNextWithGuard( ) 介面最核心的作用就是呼叫 service_executor 服務執行子模組 ( 執行緒模型子模組 ) schedule() 介面來把狀態機排程任務入隊到 ASIO 網路庫的一個全域性佇列 (adaptive 動態執行緒模型 ) ,如果是一個連結一個執行緒模型,則任務入隊到執行緒級私有佇列。

adaptive 執行緒模型,任務入隊以及工作執行緒排程任務執行的流程將在後續的執行緒模型子模組中分析,也可以參考: <<Mongodb 網路傳輸處理原始碼實現及效能調優 - 體驗核心效能極致設計 >>

   此外, _scheduleNextWithGuard( ) 入隊到全域性佇列的任務就是本模組後面需要分析的SSM 狀態機任務,這些 task 任務透過本函式介面的 func  (...) 進行封裝,然後透過執行緒模型子模組入隊到一個全域性佇列。 Func(...) 這個 task 任務中會直接呼叫 _runNextInGuard() 介面來進行狀態轉換處理,該介面也就是入隊到 ASIO 全域性佇列的任務,核心程式碼功能如下:

1. void ServiceStateMachine::_runNextInGuard(ThreadGuard guard) {  
2.     //獲取當前SSM狀態  
3.     auto curState = state();  
4.     // If this is the first run of the SSM, then update its state to Source  
5.     //如果是第一次執行該SSM,則狀態為Created,到這裡標記可以準備接收資料了  
6.     if (curState == State::Created) {   
7.         //進入Source等待接收資料  
8.         curState = State::Source;  
9.         _state.store(curState);  
10.     }  
11.     //各狀態對應處理  
12.     try {  
13.         switch (curState) {   
14.             //接收資料  
15.             case State::Source:    
16.                 _sourceMessage(std::move(guard));  
17.                 break;  
18.             //以及接收到完整的一個mongodb報文,可以內部處理(解析+命令處理+應答客戶端)  
19.             case State::Process:  
20.                 _processMessage(std::move(guard));  
21.                 break;  
22.             //連結異常或者已經關閉,則開始回收處理  
23.             case State::EndSession:  
24.                 _cleanupSession(std::move(guard));  
25.                 break;  
26.             default:  
27.                 MONGO_UNREACHABLE;  
28.         }  
29.          return;
30.     } catch (...) {  
31.         //異常列印  
32.     }  
33.     //異常處理  
34.     ......  
35.     //進入EndSession狀態  
36.     _state.store(State::EndSession);  
37.     _cleanupSession(std::move(guard));  
38. }

從上面的程式碼實現可以看出,真正入隊到全域性佇列中的任務型別只有三個,分別是:

1)  接收mongodb 資料的 task 任務,簡稱為 readTask

2)  接收到一個完整mongodb 資料後的後續處理 ( 包括協議解析、命令處理、 DB 獲取資料、傳送資料給客戶端等 ) ,簡稱為 dealTask

3)  接收或者傳送資料異常、連結關閉等引起的後續資源釋放,簡稱為cleanTask

下面針對這三種task 任務核心程式碼實現進行分析:

readTask 任務核心程式碼實現

readTask 任務核心程式碼實現由 _sourceMessage() 介面實現,具體程式碼如下:

1. //接收客戶端資料  
2. void ServiceStateMachine::_sourceMessage(ThreadGuard guard) {  
3.     ......  
4.     //獲取本session接收資料的ticket,也就是ASIOSourceTicket  
5.     auto ticket = _session()->sourceMessage(&_inMessage);   
6.     //進入等等接收資料狀態  
7.     _state.store(State::SourceWait);    
8.     //release恢復worker執行緒原有的執行緒名,synchronous執行緒模型為"conn-xx",adaptive對應worker執行緒名為"conn-xx"  
9.     guard.release();  
10.     //執行緒模型預設同步方式,也就是一個連結一個執行緒  
11.     if (_transportMode == transport::Mode::kSynchronous) {  
12.          //同步方式,讀取到一個完整mongodb報文後執行_sourceCallback回撥  
13.          _sourceCallback([this](auto ticket) {  
14.             MONGO_IDLE_THREAD_BLOCK;  
15.             return _session()->getTransportLayer()->wait(std::move(ticket));  
16.         }(std::move(ticket)));   
17.     } else if (_transportMode == transport::Mode::kAsynchronous) {  
18.         //adaptive執行緒模型,非同步讀取一個mongodb報文後執行_sourceCallback回撥  
19.         _session()->getTransportLayer()->asyncWait(   
20.             ////TransportLayerASIO::ASIOSourceTicket::_bodyCallback讀取到一個完整報文後執行該回撥  
21.             std::move(ticket), [this](Status status) { _sourceCallback(status); });  
22.     }  
23. }  
24.   
25. //接收到一個完整mongodb報文後的回撥處理  
26. void ServiceStateMachine::_sourceCallback(Status status) {  
27.     //構造ThreadGuard,修改執行本SSM介面的執行緒名為conn-xx  
28.     ThreadGuard guard(this);   
29.   
30.     //狀態檢查  
31.     dassert(state() == State::SourceWait);  
32.     //獲取連結session遠端資訊  
33.     auto remote = _session()->remote();   
34.     if (status.isOK()) {  
35.     //等待排程,進入處理訊息階段  _processMessage  
36.         _state.store(State::Process);  
37.         //注意kMayRecurse標識State::Process階段的處理還是由本執行緒執行,這是一個遞迴標記  
38.         return _scheduleNextWithGuard(std::move(guard), ServiceExecutor::kMayRecurse);  
39.     }  
40.     ......  
41.     //異常流程呼叫  
42.     _runNextInGuard(std::move(guard));  
43. }

SSM 排程的第一個任務就是 readTask 任務,從上面的原始碼分析可以看出,該任務就是透過 ticket 資料分發模組從 ASIO 網路庫讀取一個完整長度的 mongodb 報文,然後執行 _sourceCallback 回撥。進入該回撥函式後,即刻設定SSM 狀態為 State::Process 狀態,然後呼叫 _scheduleNextWithGuard (...) dealTask 任務入隊到 ASIO 的全域性佇列 (adaptive 執行緒模型 ) ,或者入隊到執行緒級私有佇列 ( synchronous 執行緒模型) 等待 worker 執行緒排程執行。

這裡有個細節,在把dealTask 入隊的時候,攜帶了 kMayRecurse 標記,該標記標識該任務可以遞迴呼叫,也就是該任務可以由當前執行緒繼續執行,這樣也就可以保證同一個請求的taskRead 任務和 dealTask 任務由同一個執行緒處理。任務遞迴排程,可以參考後面的執行緒模型子模組原始碼實現。

dealTask 任務核心程式碼實現

當讀取到一個完整長度的mongodb 報文後,就會把 dealTask 任務入隊到全域性佇列,然後由 worker 執行緒排程執行該 task 任務。 dealTask 任務的核心程式碼實現如下:

1. //dealTask處理  
2. void ServiceStateMachine::_processMessage(ThreadGuard guard) {  
3.     ......  
4.     //入口流量計數  
5.     networkCounter.hitLogicalIn(_inMessage.size());  
6.     //獲取一個唯一的UniqueOperationContext,一個客戶端對應一個UniqueOperationContext  
7.     auto opCtx = Client::getCurrent()->makeOperationContext();  
8.     //ServiceEntryPointMongod::handleRequest  ServiceEntryPointMongos::handleRequest請求處理  
9.     //command處理、DB訪問後的資料透過dbresponse返回  
10.     DbResponse dbresponse = _sep->handleRequest(opCtx.get(), _inMessage);  
11.     //釋放opCtx,這樣currentop就看不到了  
12.     opCtx.reset();  
13.     //需要傳送給客戶端的應答資料  
14.     Message& toSink = dbresponse.response;  
15.     //應答資料存在  
16.     if (!toSink.empty()) {    
17.         ......  
18.         //傳送資料 ServiceStateMachine::_sinkMessage()  
19.         _sinkMessage(std::move(guard), std::move(toSink));  
20.   
21.     } else {  
22.        //如果不需要應答客戶端的處理  
23.        ......  
24.     }  
25. }  
26.   
27. //呼叫Sinkticket傳送資料  
28. void ServiceStateMachine::_sinkMessage(ThreadGuard guard, Message toSink) {  
29.     //獲取傳送資料的ASIOSinkTicket  
30.     auto ticket = _session()->sinkMessage(toSink);  
31.     //進入sink傳送等待狀態  
32.     _state.store(State::SinkWait);  
33.     //恢復原有的worker執行緒名  
34.     guard.release();  
35.     //synchronous執行緒模型,同步傳送  
36.     if (_transportMode == transport::Mode::kSynchronous) {  
37.         //最終在ASIOSinkTicket同步傳送資料成功後執行_sinkCallback  
38.         _sinkCallback(_session()->getTransportLayer()->wait(std::move(ticket)));  
39.     } else if (_transportMode == transport::Mode::kAsynchronous) {  
40.         //最終在ASIOSinkTicket非同步傳送資料成功後執行_sinkCallback  
41.         _session()->getTransportLayer()->asyncWait(  
42.             std::move(ticket), [this](Status status) { _sinkCallback(status); });  
43.     }  
44. }  
45.   
46. //sink資料傳送  
47. void ServiceStateMachine::_sinkCallback(Status status) {  
48.     //SSM歸屬於新的guard,同時修改當前執行緒名為conn-xx  
49.     ThreadGuard guard(this);  
50.     //狀態檢查  
51.     dassert(state() == State::SinkWait);  
52.     if (!status.isOK()) {  
53.         //進入EndSession狀態  
54.         _state.store(State::EndSession);  
55.         //異常情況呼叫  
56.         return _runNextInGuard(std::move(guard));  
57.     } else if (_inExhaust) { //_inExhaust方式   
58.         //注意這裡的狀態是process   _processMessage   還需要繼續進行Process處理  
59.         _state.store(State::Process);   
60.     } else {   
61.         //正常流程始終進入該分支 _sourceMessage    這裡繼續進行遞迴接收資料處理  
62.         //注意這裡的狀態是Source,繼續接收客戶端請求  
63.         _state.store(State::Source);  
64.     }  
65.     //本連結對應的一次mongo訪問已經應答完成,需要繼續要一次排程了  
66.     return _scheduleNextWithGuard(std::move(guard),  
67.                                   ServiceExecutor::kDeferredTask |  
68.                                       ServiceExecutor::kMayYieldBeforeSchedule);  
69. }

readTask 透過 ticket 資料分發子模組讀取一個完整長度的 mongodb 報文後,開始 dealTask 任務邏輯,該任務也就是 _processMessage(...) 。該介面中核心實現就是呼叫 mongod mongos 例項對應的服務入口類的 handleRequest(...) 介面來完成後續的 command 命令處理、 DB 層資料訪問等,訪問到的資料儲存到 DbResponse 中,最終在透過 _sinkMessage(...) 把資料傳送出去。

真正的mongodb 內部處理流程實際上就是透過該 dealTask 任務完成,該任務也是處理整個請求中資源耗費最重的一個環節。在該 task 任務中,當資料成功傳送給客戶端後,該 session 連結對應的 SSM 狀態機進入 State::Source 狀態,繼續等待worker 執行緒排程來完成後續該連結的新請求。

cleanTask 任務

在資料讀寫過程、客戶端連結關閉、訪問DB 資料層等任何一個環節異常,則會進入 State::EndSession 狀態。該狀態對應得任務實現相對比較簡單,具體程式碼實現如下:

1. //session對應連結回收處理  
2. void ServiceStateMachine::_cleanupSession(ThreadGuard guard) {  
3.     //進入這個狀態後在~ThreadGuard::release中進行ssm _cleanupHook處理,該hook在ServiceEntryPointImpl::startSession  
4.     _state.store(State::Ended);  
5.     //清空message buffer  
6.     _inMessage.reset();  
7.     //釋放連結對應client資源  
8.     Client::releaseCurrent();  
}

進入該狀態後直接由本執行緒進行session 資源回收和 client 資源釋放處理,而無需狀態機排程 worker 執行緒來回收。

2.2 關於 worker 執行緒名和 guardthread 執行緒守護類

前面得分析我們知道,當執行緒模型為adaptive 動態執行緒模型的時候, mongod mongos 例項對應的子執行緒中有很多名為“ conn-xx ”和 worker-xx 的執行緒,而且同一個執行緒可能一會兒執行緒名為conn-xx ”,下一次又變為了 worker-xx 。這個執行緒名的初始命名和執行緒名更改與ServiceStateMachine 狀態機排程類、 guardthread 執行緒守護類、 worker 執行緒模型等都有關係。

Worker 執行緒由 ServiceExecutor 執行緒模型子模組建立,請參考後續執行緒模型子模組相關章節。預設初始化執行緒名為 conn-x ,初始化程式碼實現如下:

1. //ServiceStateMachine::create呼叫,ServiceStateMachine類初始化構造  
2. ServiceStateMachine::ServiceStateMachine(...)  
3.     ......  
4.     //執行緒名初始化:conn-xx,xx程式碼session id  
5.     _threadName{str::stream() << "conn-" << _session()->id()} {}   
6. }  
7.   
8. class Session {  
9.     ......  
10.     //sessionID,自增生成  
11.     const Id _id;  
12. }  
13.   
14. //全域性sessionIdCounter計數器,初始化為0  
15. AtomicUInt64 sessionIdCounter(0);  
16.   
17. //session id自增  
18. Session::Session() : _id(sessionIdCounter.addAndFetch(1)) {}

SSM 狀態處理過程中,會把一個完整的請求過程 = readTask 任務 + dealTask 任務,這兩個任務都是透過 SSM 狀態機和 ServiceExecutor 執行緒模型子模組的 worker 執行緒配合排程完成,在任務處理過程中處理同一個任務的執行緒可能會有多次執行緒名更改,這個就是結合 guardthread 執行緒守護類來完成,以一個執行緒名切換更改虛擬碼實現為例:

1. worker_thread_run_task(...)  
2. {  
3.     //如果是adaptive執行緒模型,當前worker執行緒名為"worker-xx"  
4.     print(threadName)  
5.     //業務邏輯處理1  
6.     ......  
7.       
8.     //初始化構造ThreadGuard,這裡面修改執行緒名為_ssm->_threadName,也就是"conn-xx",  
9.     //同時儲存原來的執行緒名"worker-xx"到_ssm->_oldThreadName中  
10.     ThreadGuard guard(this);  
11.     //如果是adaptive執行緒模型,執行緒名列印內容為"conn-xx"  
12.     print(threadName)  
13.     //業務邏輯處理2  
14.     ......  
15.     //恢復_ssm->_oldThreadName儲存的執行緒名"worker-xx"  
16.     guard.release();  
17.       
18.     //如果是adaptive執行緒模型,執行緒名恢復為"worker-xx"  
19.     print(threadName)  
20. }

從上面的虛擬碼可以看出,adaptive 執行緒模型對應 worker 執行緒名為 worker ,在進入 ThreadGuard guard(this) 流程後,執行緒名更改為 conn-xx 執行緒,當 guard.release() 釋放後恢復原有 worker-xx 執行緒名。

結合前面的SSM 狀態處理流程, adaptive 執行緒模型可以得到如下總結:底層網路 IO 資料讀寫過程, worker 執行緒名會改為 worker-xx ,其他非網路IO mongodb 內部邏輯處理執行緒名為 conn-xx 。所以,如果檢視mongod 或者 mongos 程式所有執行緒名的時候,如果發現執行緒名為 worker-xx ,說明當前執行緒在處理網路IO ;如果發現執行緒名為 conn-xx ,則說明當前執行緒在處理內部邏輯處理,對於mongod 例項可以理解為主要處理磁碟 IO

由於 synchronous 同步執行緒模型,同一連結對應的所有客戶端請求至始至終都有同一執行緒處理,所以整個處理執行緒名不會改變,也沒必要修改執行緒名,整個過程都是 conn-xx 執行緒名。

2.3 該模組函式介面總結大全

   前面分析了主要核心介面原始碼實現,很多其他介面沒有一一列舉詳細分析,該模組u 所有介面功能總結如下,更多介面程式碼實現詳見

3. 總結

本文主要分析了service_state_machine 狀態機子模組,該模組把 session 對應的客戶端請求轉換為 readTask 任務、 dealTask 任務和 cleanTask 任務,前兩個任務透過 worker 執行緒完成排程處理, cleanTask 任務在內部處理異常或者連結關閉的時候由本執行緒直接執行,而不是透過 worker 執行緒排程執行。

這三個任務處理過程會分別對應到Created Source SourceWait Process SinkWait EndSession Ended 七種狀態的一種或者多種,具體詳見前面的狀態碼分析。一個正常的客戶端請求狀態轉換過程如下 :

1)  連結剛建立的第一次請求狀態轉換過程:

Created->Source -> SourceWait -> Process -> SinkWait -> Source

2)  該連結後續的請求狀態轉換過程:

 Source -> SourceWait -> Process -> SinkWait -> Source

此外,SSM 狀態機排程模組透過 ServiceStateMachine::_scheduleNextWithGuard(...) 介面和執行緒模型子模組關聯起來。 SSM 透過該介面完成 worker 執行緒初始建立、 task 任務入隊處理,下期將分析 << 網路執行緒模型子模組 >> 詳細原始碼實現。

說明:

該模組更多介面實現細節詳見Mongodb 核心原始碼註釋:



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984922/viewspace-2730526/,如需轉載,請註明出處,否則將追究法律責任。

相關文章