摘要:旨在實(shí)現(xiàn)一個(gè)輕量并且能夠保證性能的,僅關(guān)注的本質(zhì)部分。定時(shí)器如果一個(gè)請求在建立連接后遲遲沒有發(fā)送數(shù)據(jù),或者對方突然斷電,應(yīng)該如何處理我們需要實(shí)現(xiàn)定時(shí)器來處理超時(shí)的請求。
最近重構(gòu)了去年造的一個(gè)輪子?Vino。Vino 旨在實(shí)現(xiàn)一個(gè)輕量并且能夠保證性能的 Web Server,僅關(guān)注 Web Server 的本質(zhì)部分。在重構(gòu)過程中,Vino 借鑒了許多優(yōu)秀開源項(xiàng)目的思想,如 Nginx、Mongoose 和 Webbench。因此,對比上一個(gè)版本的 Vino,現(xiàn)在的 Vino 不僅性能得到提升,而且設(shè)計(jì)也更為優(yōu)雅、健壯 :D。
本文將會對 Vino 目前所具備的關(guān)鍵特性進(jìn)行闡述,并總結(jié)開發(fā)過程中的一點(diǎn)心得。
單線程 + Non-BlockingVino 整體采用了基于事件驅(qū)動的單線程 + Non-Blocking 模型。采用單線程模型,避免了系統(tǒng)分配多線程及線程之間通信的開銷,同時(shí)降低了內(nèi)存的耗用。由于采用了單線程模型,為了更好的提高線程利用率,Vino 將默認(rèn) Blocking 的 I/O 設(shè)置為 Non-Blocking I/O,即在線程讀/寫數(shù)據(jù)的過程中,如果緩沖區(qū)為空/緩沖區(qū)滿,線程不會阻塞,而是立即返回,并設(shè)置 errno。
Vino 最初的靈感來源于 Computer Systems: A Programmer"s Perspective 一書講述網(wǎng)絡(luò)編程時(shí)實(shí)現(xiàn)的一個(gè)簡單的 Web Server,每到來一個(gè)請求,Web Server 都會 fork 一個(gè)進(jìn)程去處理。顯然,在高并發(fā)的場景下,這種模型是不合理的。每次 fork 進(jìn)程會帶來巨大的開銷,并且系統(tǒng)中進(jìn)程的數(shù)量是有限的。同時(shí),伴隨多進(jìn)程帶來的進(jìn)程調(diào)度的開銷也不可小覷,CPU 會花費(fèi)大量的時(shí)間用于決定調(diào)用哪一個(gè)進(jìn)程。進(jìn)程調(diào)度引發(fā)的進(jìn)程上下文之間的切換,也需要耗費(fèi)相當(dāng)大的資源。
很容易聯(lián)想到采用多線程模型來替代多進(jìn)程模型,相比于多進(jìn)程模型,多線程模型占用的系統(tǒng)資源會大大降低,但是本質(zhì)上并沒有減小線程調(diào)度帶來的開銷。為了減小由線程調(diào)度導(dǎo)致的開銷,我們可以采用線程池模型,即固定線程的數(shù)量,但是問題依舊存在:因?yàn)?Linux 默認(rèn) I/O 是阻塞(Blocking)的,如果線程池中所有的線程同時(shí)阻塞于正在處理的請求,那么新到來的請求就沒有線程去處理了。因此,如果我們用 Non-Blocking 的 I/O 替換默認(rèn)的 Blocking I/O,線程將不會阻塞于數(shù)據(jù)的讀寫,問題便可得到解決。
HTTP Keep-AliveVino 支持 HTTP 長連接(Persistent Connections),即多個(gè)請求可以復(fù)用同一個(gè) TCP 連接,以此減少由 TCP 建立/斷開連接所帶來的性能開銷。每到來一個(gè)請求,Vino 會對請求進(jìn)行解析,判斷請求頭中是否存在 Connection: keep-alive 請求頭。如果存在,在處理完一個(gè)請求后會保持連接,并對數(shù)據(jù)緩沖區(qū)(用于保存請求內(nèi)容,響應(yīng)內(nèi)容)及狀態(tài)標(biāo)記進(jìn)行重置,否則,關(guān)閉連接。
關(guān)于 HTTP Keep-Alive 的優(yōu)勢,RFC 2616 有著更完善的總結(jié),引用如下。
定時(shí)器 TimerBy opening and closing fewer TCP connections, CPU time is saved in routers and hosts (clients, servers, proxies, gateways, tunnels, or caches), and memory used for TCP protocol control blocks can be saved in hosts.
HTTP requests and responses can be pipelined on a connection. Pipelining allows a client to make multiple requests without waiting for each response, allowing a single TCP connection to be used much more efficiently, with much lower elapsed time.
Network congestion is reduced by reducing the number of packets caused by TCP opens, and by allowing TCP sufficient time to determine the congestion state of the network.
Latency on subsequent requests is reduced since there is no time spent in TCP"s connection opening handshake.
HTTP can evolve more gracefully, since errors can be reported without the penalty of closing the TCP connection. Clients using future versions of HTTP might optimistically try a new feature, but if communicating with an older server, retry with old semantics after an error is reported.
如果一個(gè)請求在建立連接后遲遲沒有發(fā)送數(shù)據(jù),或者對方突然斷電,應(yīng)該如何處理?我們需要實(shí)現(xiàn)定時(shí)器來處理超時(shí)的請求。Vino 定時(shí)器的實(shí)現(xiàn)參考了 Nginx 的設(shè)計(jì),Nginx 使用一顆紅黑樹來存儲各個(gè)定時(shí)事件,每次事件循環(huán)時(shí)從紅黑樹中不斷找出最?。ㄔ纾┑氖录?,如果超時(shí)則觸發(fā)超時(shí)處理。為了簡化實(shí)現(xiàn),在 Vino 中,我實(shí)現(xiàn)了一個(gè)小頂堆來存儲定時(shí)事件,如果被處理的定時(shí)事件同時(shí)支持長連接,那么在該請求處理完畢后會更新該請求對應(yīng)的定時(shí)器,也就是重新計(jì)時(shí)。定時(shí)器相關(guān)代碼見 vn_event_timer.h 和 vn_event_timer.c。
HTTP Parser由于網(wǎng)絡(luò)的不確定性,我們并不能保證一次就能讀取所有的請求數(shù)據(jù)。因此,對于每一個(gè)請求,我們都會開辟一段緩沖區(qū)用于保存已經(jīng)讀取到的數(shù)據(jù)。同時(shí),我們需要同時(shí)對讀取到的數(shù)據(jù)進(jìn)行解析,以保證讀取到的數(shù)據(jù)都是合理的數(shù)據(jù),例如,假設(shè)目前緩沖區(qū)內(nèi)的數(shù)據(jù)為 GET /index.html HTT,那么下一次讀取到的字符必須為 P,否則,應(yīng)立即檢測出當(dāng)前請求是一個(gè)異常的請求,并主動關(guān)閉當(dāng)前的連接。
基于以上分析,我們需要實(shí)現(xiàn)一個(gè) HTTP 狀態(tài)機(jī)(Parser)來維持當(dāng)前的解析狀態(tài),Vino 狀態(tài)機(jī)的實(shí)現(xiàn)參考了 Nginx 的設(shè)計(jì),并對 Nginx 的實(shí)現(xiàn)做了簡化。HTTP Parser 相關(guān)代碼見 vn_http_parse.h 和 vn_http_parse.c。
Memory Pool我們一般使用 malloc/calloc/free 來分配/釋放內(nèi)存,但是這些函數(shù)對于一些需要長時(shí)間運(yùn)行的程序來說會有一些弊端。頻繁使用這些函數(shù)分配和釋放內(nèi)存,會導(dǎo)致內(nèi)存碎片,不容易讓系統(tǒng)直接回收內(nèi)存。典型的例子就是大并發(fā)頻繁分配和回收內(nèi)存,會導(dǎo)致進(jìn)程的內(nèi)存產(chǎn)生碎片,并且不會立馬被系統(tǒng)回收。
使用內(nèi)存池分配內(nèi)存,可以在一定程度上提升內(nèi)存分配的效率,不需要每次都調(diào)用 malloc/calloc 函數(shù)。同時(shí),使用內(nèi)存池使得內(nèi)存管理更加簡單。在 Vino 中,針對每一個(gè)請求,Vino 都會為其分配一或多個(gè)內(nèi)存池(各個(gè)內(nèi)存池形成一個(gè)單鏈表),在請求處理完畢后,一并釋放所有的內(nèi)存。
Vino 內(nèi)存池的實(shí)現(xiàn)依舊參考了 Nginx 的實(shí)現(xiàn),并做了簡化,Memory Pool 相關(guān)代碼見 vn_palloc.h 和 vn_palloc.c。
其他在開發(fā) Vino 的過程中,還有許多需要考慮和權(quán)衡的地方。響應(yīng)請求時(shí),如果用戶請求的是一個(gè)很大的文件,導(dǎo)致寫緩沖區(qū)滿,我們?nèi)绾胃玫脑O(shè)計(jì)響應(yīng)緩沖區(qū)?如何更高效的設(shè)計(jì)底層數(shù)據(jù)結(jié)構(gòu)(如字符串、鏈表、小頂堆等)?如何更優(yōu)雅的解析命令行參數(shù)?如何對特定信號進(jìn)行處理?如何更健壯的處理錯誤信息?當(dāng)代碼的數(shù)量達(dá)到一定程度后,如何更快的定位異常代碼?
Vino 的開發(fā) & 重構(gòu)暫時(shí)告一段落,源碼放在了 GitHub 上。當(dāng)然,Vino 還有許多不足之處,以及未實(shí)現(xiàn)的特性。
僅支持 HTTP GET 方法,暫不支持其他 HTTP method。
暫不支持動態(tài)請求的處理。
支持的 HTTP/1.1 特性有限。
...
寫這篇文章,希望對初學(xué)者有所幫助。
參考[1] Vino, https://github.com/tinylcy/vino .
[2] Computer Systems: A Programmer"s Perspective, http://csapp.cs.cmu.edu/ .
[3] Advanced Programming in the UNIX Environment (3rd Edition), https://www.amazon.ca/Advance... .
[4] Unix Network Programming, Volume 1, https://www.amazon.ca/Unix-Ne... .
[5] Nginx, https://github.com/nginx/nginx .
[6] Mongoose, https://github.com/cesanta/mo... .
[7] Web Bench, http://home.tiscali.cz/~cz210... .
[8] Zaver, https://github.com/zyearn/zaver .
[9] RFC 2616, https://tools.ietf.org/html/r... .
[10] How to use epoll? A complete example in C, https://banu.com/blog/2/how-t... .
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/39725.html
摘要:提出這些規(guī)范的目的就是為了定義統(tǒng)一的標(biāo)準(zhǔn),提升程序的可移植性。的對象處理完成后,會調(diào)用并且返回結(jié)果給,存放在中。比如可以檢查是否有非法內(nèi)容,檢查是否有非法內(nèi)容,為加上特定的等,這些都是可以的。的實(shí)現(xiàn)和部署要使用,需要分別實(shí)現(xiàn)角色和角色。 WSGI是什么 WSGI的全稱是Web Server Gateway Interface,翻譯過來就是Web服務(wù)器網(wǎng)關(guān)接口。具體的來說,WSGI是一個(gè)...
摘要:也就是說,是連接服務(wù)器和應(yīng)用服務(wù)器的橋梁。目前實(shí)現(xiàn)的中,有兩個(gè)角色分別是和。是一個(gè)可調(diào)用對象,它包含了一個(gè)表示響應(yīng)狀態(tài)的字符串和一個(gè)響應(yīng)的列表以及一個(gè)用于出錯返回的信息,具體參數(shù)包含及詳情請點(diǎn)這里。可以被連接在一起,由此產(chǎn)生的鏈通常被稱為。 首先,什么是WSGI? WSGI, 全稱為 Web Server Gateway Interface。 它不是什么框架,它是一個(gè)規(guī)范,但是作為一個(gè)...
摘要:在只有少量服務(wù)器的情況下,大多數(shù)運(yùn)維人員會選擇手動更新,減少自動化部署發(fā)布的開發(fā)成本。五自動化部署發(fā)布示例簡要說明下我們的項(xiàng)目如何使用實(shí)現(xiàn)了多個(gè)環(huán)境的自動化部署發(fā)布。 showImg(https://segmentfault.com/img/bVJEL6?w=500&h=250); 一、面臨的問題 一個(gè)完整的程序開發(fā)流程少不了部署發(fā)布這個(gè)環(huán)節(jié),而部署發(fā)布是一個(gè)重復(fù)的過程,最基本的操作包...
摘要:首發(fā)于我的博客前言從開始,就提供了一個(gè)內(nèi)置的服務(wù)器。在中的安裝一節(jié)中介紹了一個(gè)命令可以使用內(nèi)置服務(wù)器實(shí)現(xiàn)外部訪問的命令。 首發(fā)于:我的博客 前言 PHP從5.4開始,就提供了一個(gè)內(nèi)置的web服務(wù)器。 這個(gè)主要是用來做本地的開發(fā)用的。不能用于線上環(huán)境?,F(xiàn)在我就介紹一下這個(gè)工具如何使用。 基礎(chǔ)應(yīng)用 首先我們假定項(xiàng)目目錄是/home/baoguoxiao/www/php/demo,外界可訪...
閱讀 1067·2021-11-22 15:35
閱讀 1703·2021-10-26 09:49
閱讀 3244·2021-09-02 15:11
閱讀 2092·2019-08-30 15:53
閱讀 2644·2019-08-30 15:53
閱讀 2940·2019-08-30 14:11
閱讀 3539·2019-08-30 12:59
閱讀 3252·2019-08-30 12:53