服務(wù)器實戰(zhàn) (一):前置知識與功能目標(biāo))
系列文章目錄文章目錄系列文章目錄前言一、項目認(rèn)識二、知識背景2.1 HTTP服務(wù)器2.2 Reactor模型三、選擇的原因3.1 單 Reactor 單線程單 I/O 多路復(fù)用 業(yè)務(wù)處理3.2 單 Reactor 多線程單 I/O 多路復(fù)用 線程池業(yè)務(wù)處理3.3 多 Reactor 多線程多 I/O 多路復(fù)用 線程池業(yè)務(wù)處理四、項目解析4.1 SERVER模塊4.2 HTTP協(xié)議模塊總結(jié)前言大家好我是曦夜日長。經(jīng)過這么長時間的學(xué)習(xí)也是修得正果正式來到我們的標(biāo)準(zhǔn)項目欄目正如專欄所示本系列實現(xiàn)仿muduo庫OneThreadOneLoop式主從Reactor模型實現(xiàn)?并發(fā)服務(wù)器從零到有的開發(fā)。請跟著我的腳步開始我們第一個項目的開發(fā)。一、項目認(rèn)識我們要開發(fā)的項目服務(wù)器可以說是最經(jīng)典的服務(wù)器模板這里給出百度資料主從 Reactor OneThreadOneLoop 模型至今仍廣泛用于工業(yè)級高性能服務(wù)C 生態(tài) muduo、brpcJava 生態(tài) Netty 均采用同類架構(gòu)被應(yīng)用在即時通訊 IM、游戲網(wǎng)關(guān)、流媒體服務(wù)器、IoT 設(shè)備網(wǎng)關(guān)、RPC 網(wǎng)關(guān)等海量長連接場景。當(dāng)然我們開發(fā)的只是服務(wù)器底層其中內(nèi)部業(yè)務(wù)的實現(xiàn)還需要我們自己部署作為主從Reactor它有著這樣的特性主Reactor獲取到新連接后分發(fā)給?Reactor進(jìn)?通信事件監(jiān)控。而子Reactor線程監(jiān)控各?的描述符的讀寫事件進(jìn)?數(shù)據(jù)讀寫以及業(yè)務(wù)處理。在談及這樣的Reactor模型One Thread OneLoop常被重點標(biāo)注其的思想就是把所有的操作都放到?個線程中進(jìn)行?個線程對應(yīng)?個事件處理的循環(huán)。借上面的圖我們來分析一下具體的流程客戶端發(fā)起 TCP 連接請求多個客戶端向服務(wù)器監(jiān)聽端口發(fā)起連接。主 ReactormainReactor只負(fù)責(zé)監(jiān)聽 accept 事件mainReactor 內(nèi)部 epoll 監(jiān)聽 listenfd捕獲新連接事件調(diào)用accept()拿到新客戶端 socket 的 fd完成三次握手得到 TcpConnection。新連接分發(fā)mainReactor 通過負(fù)載均衡策略muduo 是輪詢將這個新連接 fd投遞到其中一個從屬 Reactor (subReactor)。從屬 Reactor 接管連接注冊 IO 事件被選中的 subReactor一個線程對應(yīng)一個 EventLoopOneThreadOneLoop把這個新 fd 注冊進(jìn)自己內(nèi)部的 epoll后續(xù)這個連接永久綁定該 subReactor不會切換線程。自此mainReactor 不再管這條連接只繼續(xù)等待下一個新客戶端連接。從屬 Reactor 循環(huán)監(jiān)控 IO 事件subReactor 的 epoll 持續(xù)等待事件客戶端發(fā)數(shù)據(jù) → socket可讀事件觸發(fā)內(nèi)核緩沖區(qū)可寫入數(shù)據(jù) → socket可寫事件觸發(fā)連接斷開 → 關(guān)閉事件。事件回調(diào)處理subReactor 線程檢測到可讀事件在當(dāng)前 IO 線程內(nèi)執(zhí)行讀操作從 socket 讀取數(shù)據(jù)直接在這個 IO 線程調(diào)用onMessage業(yè)務(wù)回調(diào)解析數(shù)據(jù)包、執(zhí)行業(yè)務(wù)邏輯業(yè)務(wù)處理完成后如果需要回復(fù)客戶端在當(dāng)前線程執(zhí)行 send 寫數(shù)據(jù)。連接關(guān)閉當(dāng)檢測到連接關(guān)閉事件subReactor 在本線程清理該 TcpConnection 資源從 epoll 移除 fd。二、知識背景我們想實現(xiàn)這樣一個項目主要依靠的是我們之前學(xué)習(xí)的HTTP服務(wù)器和Reactor事件派發(fā)器現(xiàn)在爆發(fā)鬼腦回想一下2.1 HTTP服務(wù)器HTTPHyperTextTransferProtocol超?本傳輸協(xié)議是應(yīng)?層協(xié)議是?種簡單的請求-響應(yīng)協(xié)議但我們要明白的是它只是服務(wù)于應(yīng)用層的數(shù)據(jù)傳輸方式根本還是在于TCP為基礎(chǔ)搭建的服務(wù)器上。因此可以說HTTP服務(wù)器本質(zhì)就是個TCP服務(wù)器所以就需要socket-bind-listen-accept這幾個我們熟知的流程在內(nèi)部對數(shù)據(jù)HTTP協(xié)議封裝、調(diào)用業(yè)務(wù)邏輯當(dāng)然在當(dāng)前項目中的服務(wù)器本?并不存在業(yè)務(wù)。2.2 Reactor模型Reactor 模式是指通過?個或多個輸?同時傳遞給服務(wù)器進(jìn)?請求處理時的事件驅(qū)動處理模式服務(wù)端程序處理傳?多路請求并將它們同步分派給請求對應(yīng)的處理線程。又名Dispatcher 模式基礎(chǔ)的Reactor模型的思路與基礎(chǔ)的TcpServer的阻塞式IO不同采用多路復(fù)用的方法這兩者的區(qū)別就是一直等和等多個比如釣魚張三只帶了一個魚竿釣魚時只能等著魚上鉤而多路復(fù)用則是李四帶了多個魚竿等著只要有上鉤就去處理。但這兩種方法本質(zhì)區(qū)別是等的區(qū)別處理事件過程是相同的。Reactor基本思想是監(jiān)控fd如果fd就緒就告訴內(nèi)核進(jìn)而響應(yīng)到用戶應(yīng)用層處理后重新等待。這里就不講述過多了詳細(xì)內(nèi)容可以查看有關(guān)的博客我的沒寫:(三、選擇的原因那就有人要疑惑了我之前不是實現(xiàn)了一個Reactor服務(wù)器嗎那為什么還有跟著你的項目寫一個我寫過的東西。其實不然我們之前實現(xiàn)的只是一個單Reactor 多線程的模式它有著這樣的一種缺陷同一個連接 fd 上先后觸發(fā)的 IO 事件會被封裝為獨立任務(wù)直接投遞至全局線程池線程池不保證任務(wù)完成順序與事件入隊順序一致若后提交的請求任務(wù)執(zhí)行更快會優(yōu)先返回響應(yīng)造成同一連接上請求與響應(yīng)順序錯亂引發(fā)協(xié)議解析異常。因此我們不可能使用這樣一種具有嚴(yán)重處理缺陷的代碼作為能夠勝任高并發(fā)服務(wù)器開發(fā)的基礎(chǔ)實現(xiàn)。那讓我們認(rèn)識一下Reactor的種類3.1 單 Reactor 單線程單 I/O 多路復(fù)用 業(yè)務(wù)處理這是最樸素的一種實現(xiàn)整個服務(wù)器就一個線程既負(fù)責(zé)監(jiān)聽事件又負(fù)責(zé)干活。工作流程通過 I/O 多路復(fù)用模型進(jìn)行客戶端請求監(jiān)控。觸發(fā)事件后進(jìn)行事件處理a. 如果是新建連接請求則獲取新建連接并添加至多路復(fù)用模型進(jìn)行事件監(jiān)控。b. 如果是數(shù)據(jù)通信請求則進(jìn)行對應(yīng)的數(shù)據(jù)處理接收數(shù)據(jù)、處理數(shù)據(jù)、發(fā)送響應(yīng)。優(yōu)點所有操作均在同一線程中完成思想流程較為簡單不涉及進(jìn)程/線程間通信及資源爭搶問題。缺點無法有效利用 CPU 多核資源很容易達(dá)到性能瓶頸。適用場景適用于客戶端數(shù)量較少、且處理速度較為快速的場景。處理較慢或活躍連接較多時會導(dǎo)致串行處理后處理的連接長時間無法得到響應(yīng)??梢钥吹剿暮唵问强繝奚l(fā)能力換來的——一旦某個業(yè)務(wù)處理耗時變長整個服務(wù)器都會被它拖住因為它根本沒有第二個線程能接手。3.2 單 Reactor 多線程單 I/O 多路復(fù)用 線程池業(yè)務(wù)處理既然單線程扛不住最直接的想法就是把監(jiān)聽和干活拆開干活交給線程池。這就是單 Reactor 多線程模式。工作流程Reactor 線程通過 I/O 多路復(fù)用模型進(jìn)行客戶端請求監(jiān)控。觸發(fā)事件后進(jìn)行事件處理a. 如果是新建連接請求則獲取新建連接并添加至多路復(fù)用模型進(jìn)行事件監(jiān)控。b. 如果是數(shù)據(jù)通信請求則接收數(shù)據(jù)后分發(fā)給 Worker 線程池進(jìn)行業(yè)務(wù)處理。c. 工作線程處理完畢后將響應(yīng)交給 Reactor 線程進(jìn)行數(shù)據(jù)響應(yīng)。優(yōu)點充分利用 CPU 多核資源。缺點多線程間的數(shù)據(jù)共享訪問控制較為復(fù)雜且單個 Reactor 承擔(dān)所有事件的監(jiān)聽和響應(yīng)在單線程中運(yùn)行高并發(fā)場景下容易成為性能瓶頸。注意第 2.c 步這個細(xì)節(jié)——響應(yīng)最終還是要回到 Reactor 線程去發(fā)送。這既帶來了各線程都要和 Reactor 打交道的同步成本也正是我們在上一節(jié)踩過的坑所在業(yè)務(wù)任務(wù)一旦并發(fā)投遞進(jìn)線程池同一連接上請求的處理與響應(yīng)的先后順序就沒人保證了順序錯亂、協(xié)議解析異常隨之而來。3.3 多 Reactor 多線程多 I/O 多路復(fù)用 線程池業(yè)務(wù)處理單 Reactor 的瓶頸在于所有事件的監(jiān)聽和響應(yīng)都壓在同一個線程上。那思路就很清楚了把 Reactor 本身也拆開讓它多干活。這就是多 Reactor 多線程模式也就是常說的主從 ReactorMain-Sub Reactor。工作流程在主 Reactor中處理新連接請求事件有新連接到來則分發(fā)到子 Reactor中監(jiān)控。在子 Reactor中進(jìn)行客戶端通信監(jiān)控有事件觸發(fā)則接收數(shù)據(jù)分發(fā)給Worker 線程池。Worker 線程池分配獨立的線程進(jìn)行具體的業(yè)務(wù)處理。a. 工作線程處理完畢后將響應(yīng)交給子 Reactor線程進(jìn)行數(shù)據(jù)響應(yīng)。優(yōu)點充分利用 CPU 多核資源主從 Reactor 各司其職。缺點實現(xiàn)復(fù)雜度最高——連接如何分發(fā)、線程間如何通信、生命周期如何管理都需要自己設(shè)計好代碼量和對細(xì)節(jié)的把握要求也遠(yuǎn)高于前兩種。與前兩種模式相比它最大的變化是主 Reactor 只負(fù)責(zé)接客子 Reactor 負(fù)責(zé)服務(wù)。新連接被主 Reactor 接進(jìn)來后會被分配給某一個子 Reactor此后這條連接上的所有 I/O 事件都由這個子 Reactor 處理?;氐介_頭那個問題為什么我們要在已經(jīng)有一個單 Reactor 多線程的服務(wù)器之后再寫一個主從 Reactor答案就在 3.3 的分工結(jié)構(gòu)里——同一個連接 fd 上的事件從此由同一個子 Reactor 線程順序處理。既不會出現(xiàn)后提交的任務(wù)先返回的亂序問題又因為子 Reactor 可以有多個把 CPU 多核真正用了起來同時主 Reactor 只做分發(fā)、不做業(yè)務(wù)保證它不會被慢業(yè)務(wù)拖住能持續(xù)穩(wěn)定地接收新連接。所以主從 Reactor 不是更花哨的寫法而是為了解決單 Reactor 線程池在連接級順序性和高并發(fā)擴(kuò)展性上的雙重缺陷——這也正是我們選擇它的原因。四、項目解析基于以上的理解我們要實現(xiàn)的是?個帶有協(xié)議?持的Reactor模型?性能服務(wù)器因此將整個項?的實現(xiàn)劃分為兩個?的模塊SERVER模塊實現(xiàn)Reactor模型的TCP服務(wù)器協(xié)議模塊對當(dāng)前的Reactor模型服務(wù)器提供應(yīng)?層協(xié)議?持4.1 SERVER模塊SERVER模塊就是對所有的連接以及線程進(jìn)?管理讓它們各司其職在合適的時候做合適的事最終完成?性能服務(wù)器組件的實現(xiàn)。并且這些管理可以詳細(xì)分為監(jiān)聽連接管理對監(jiān)聽連接進(jìn)?管理。通信連接管理對通信連接進(jìn)?管理。超時連接管理對超時連接進(jìn)?管理。而這個大模塊就可以分成多個小模塊組裝實現(xiàn)Buff模塊緩沖區(qū)模塊?于實現(xiàn)通信中??態(tài)的接收緩沖區(qū)和發(fā)送緩沖區(qū)功能Socket模塊負(fù)責(zé)服務(wù)器的系列操作Channel模塊對每個連接進(jìn)行監(jiān)控的模塊實現(xiàn)對描述符可讀可寫錯誤…事件的管理操作以及Poller模塊對描述符進(jìn)?IO事件監(jiān)控就緒后根據(jù)不同的事件回調(diào)不同的處理函數(shù)功能Poller模塊是對epoll模型的封裝實現(xiàn)對事件的添加、修改和刪除等功能Connection模塊是對Buff、Socket、Channel模塊的封裝使得可以管理所有的連接Acceptor模塊Acceptor模塊是對Socket模塊Channel模塊的?個整體封裝實現(xiàn)了對?個監(jiān)聽套接字的整體的管理TimerQueue模塊是實現(xiàn)固定時間定時任務(wù)的模塊可以理解就是要給定時任務(wù)管理器向定時任務(wù)管理器中添加?個任務(wù)任務(wù)將在固定時間后被執(zhí)?同時也可以通過刷新定時任務(wù)來延遲任務(wù)的執(zhí)?EventLoop模塊可以理解就是我們上邊所說的Reactor模塊它是對Poller模塊TimerQueue模塊Socket模塊的?個整體封裝進(jìn)?所有描述符的事件監(jiān)控TcpServer模塊這個模塊是?個整體Tcp服務(wù)器模塊的封裝內(nèi)部封裝了Acceptor模塊EventLoopThreadPool模塊4.2 HTTP協(xié)議模塊HTTP協(xié)議模塊?于對?并發(fā)服務(wù)器模塊進(jìn)?協(xié)議?持基于提供的協(xié)議?持能夠更?便的完成指定協(xié)議服務(wù)器的搭建。?HTTP協(xié)議?持模塊的實現(xiàn)可以細(xì)分為以下?個模塊。Util模塊這個模塊是?個?具模塊主要提供HTTP協(xié)議模塊所?到的?些?具函數(shù)?如url編解碼?件讀寫…等。HttpRequest模塊這個模塊是HTTP請求數(shù)據(jù)模塊?于保存HTTP請求數(shù)據(jù)被解析后的各項請求元素信息。HttpResponse模塊這個模塊是HTTP響應(yīng)數(shù)據(jù)模塊?于業(yè)務(wù)處理后設(shè)置并保存HTTP響應(yīng)數(shù)據(jù)的的各項元素信息最終會被按照HTTP協(xié)議響應(yīng)格式組織成為響應(yīng)信息發(fā)送給客?端。HttpContext模塊這個模塊是?個HTTP請求接收的上下?模塊主要是為了防?在?次接收的數(shù)據(jù)中不是?個完整的HttpServer模塊這個模塊是最終給組件使?者提供的HTTP服務(wù)器模塊了?于以簡單的接?實現(xiàn)HTT服務(wù)器的搭建。在之后具體的模塊開發(fā)時會對每個模塊的具體實現(xiàn)做出規(guī)劃并解釋功能與意義以便我們更好的梳理項目結(jié)構(gòu)??偨Y(jié)我們的項目前提就到這里了下期我們將學(xué)習(xí)一些項目中需要實現(xiàn)的小功能模塊正式開啟我們的項目實現(xiàn)感謝您的收看讓我們下期再見。