文章目錄
相關基礎
事件驅動模型詳解
通常,我們寫服務器處理模型的程序時,有以下幾種模型:
- (1)每收到一個請求,創建一個新的進程,來處理該請求;
- (2)每收到一個請求,創建一個新的線程,來處理該請求;
- (3)每收到一個請求,放入一個事件列表,讓主進程通過非阻塞I/O方式來處理請求
上面的幾種方式,各有千秋,
- 第(1)中方法,由於創建新的進程的開銷比較大,所以,會導致服務器性能比較差,但實現比較簡單。
- 第(2)種方式,由於要涉及到線程的同步,有可能會面臨死鎖等問題。
- 第(3)種方式,在寫應用程序代碼時,邏輯比前面兩種都複雜。
綜合考慮各方面因素,一般普遍認爲第(3)種方式是大多數網絡服務器採用的方式
什麼是事件驅動模型
事件驅動模型是指:有一個事件隊列,或者說消息隊列,我們的事件源往這個隊列裏面添加任務,然後有個循環,一直在這個隊列裏面取出事件,根據不同的事件調用不同的函數,就叫事件驅動模型,它是一種編程範式。
我們的UI編程,大部分都是事件驅動模型,比如我們寫JavaScript的onClick(),鼠標按下的時候,鼠標點擊的事件添加到隊列裏面,然後取到這個事件後,調用onClick()函數。如下圖
讓我們用例子來比較和對比一下單線程、多線程以及事件驅動編程模型。下圖展示了隨着時間的推移,這三種模式下程序所做的工作。這個程序有3個任務需要完成,每個任務都在等待I/O操作時阻塞自身。阻塞在I/O操作上所花費的時間已經用灰色框標示出來了。
三種編程模型的比較
單線程模型是阻塞的,如果每個任務之間都是沒有關聯的,很顯然它降低了運行速度,因爲只要一個任務阻塞了,其他任務都要等待,等它運行完,其他任務才能運行,阻塞的這段時間嚴重影響了運行速度。
多線程模型,多個任務分別在多個獨立的線程之間執行,程序最終的運行時間約等於運行時間最長的那個線程。多線程模型在多處理器的系統中可以並行執行,在單處理器系統中交錯執行,這使得當某個線程阻塞的時候,其他線程可以繼續執行,但是當多個線程同時修改同一份數據的時候,往往要考慮線程安全問題,必須寫代碼保護共享數據,防止被多個線程同時訪問,,需要加鎖等機制處理線程安全問題
事件驅動模型裏面3個任務交錯執行,但是還是在同一個線程裏面(協程),當處理I/O或者其他昂貴的操作時,註冊一個回調到事件循環中,然後當I/O操作完成時繼續執行。簡單來講,事件驅動模型是一遇到IO就註冊一個事件,然後主程序就可以幹其他事情,直到IO處理完,繼續恢復之前中斷的任務。
事件驅動模型I/O爲什麼不阻塞呢
因爲IO操作是用操作系統完成的,咋們用戶讀一個文件,並不是我們自己的程序打開一個文件,然後去把文件的內容讀出來。而是操作系統的調度接口打開這個文件,然後把這個數據讀會開,其實是操作系統負責IO的控制。
I/O結束怎麼切換回來
加一個回調函數,就是我去切換之前,調操作系統IO接口的時候,告訴操作系統,說你處理完了之後,調一下這個回調函數,這個回調函數就會通知我,通知我了就代表執行完了,我就回來把這個IO拿到了,所以就是通過這個事件驅動的方式。出現這個IO操作,我就註冊這個事件,就是IO事件交給操作系統,操作系統內部有一個隊列,處理完了吧結果返回給你,通知回調函數通知你。
事件驅動模型邏輯圖
I/O多路複用
概念說明
用戶空間與內核空間
爲了保證用戶不能直接操作操作系統的內核,保證內核的安全,操作系統把虛擬空間分成內核空間和用戶空間,這個劃分不是一刀切的,而是通過CPU指令集裏面的寄存器的狀態位來區分的,在CPU裏面有一塊指令集,這個指令集裏面有一個寄存器,這個寄存器的狀態位是0,代表是內核態,是1代表是用戶態
文件描述符
文件描述符是指向文件引用的概念,文件描述符在形式上一般是一個非負整數,其實他是一個索引值,當程序打開一個文件,或者創建一個文件的時候,內核會向進程返回一個文件描述符
緩存I/O
緩存I/O又叫標準I/O,大多數文件系統的默認I/O操作都是緩存I/O,在 Linux 的緩存 I/O 機制中,操作系統會將 I/O 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據要先copy到操作系統內核的緩衝區,然後纔會從操作系統內核衝區copy到應用程序的地址空間
緩存I/O的缺點:數據在傳輸過程中需要在應用程序地址空間和內核進行多次數據拷貝操作,這些數據拷貝操作所帶來的 CPU 以及內存開銷是非常大的
socket粘包也是因爲緩存I/O?
爲了減少從內核態到用戶態的數據來回的copy,因爲如果你打開一個文件,你讀到內存裏,你以爲是直接讀到你的用戶的內存裏面,其實是先讀到緩存裏面,也就是內核的緩存裏面,然後再由內核幫你把這份數據copy到用戶的內存裏面。就是爲了避免這裏的來回copy,耗資源,這樣的話效率就高。
IO模型
阻塞IO
在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:
當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據(對於網絡IO來說,很多時候數據在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程需要等待,也就是說數據被拷貝到操作系統內核的緩衝區中是需要一個過程的。而在用戶進程這邊,整個進程會被阻塞(當然,是進程自己選擇的阻塞)。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,然後kernel返回結果,用戶進程才解除block的狀態,重新運行起來。
所以,blocking IO的特點就是在IO執行的兩個階段都被block了。
非阻塞I/O
linux下,可以通過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:
當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那麼它並不會block用戶進程,而是立刻返回一個error。,返回error,用戶進程就知道數據還沒有準備好,就可以去處理其他任務了,然後過一小段時間,它再次發送read操作,如果內核的數據準備好了,就可以把數據copy到用戶空間,返回給用戶進程。
特點:它的特點是,相比阻塞IO,在等待數據的這個階段是不阻塞的,因爲它收到error之後,就可以處理其他任務,然後再次發送read操作,當然它的那個從數據從內核空間copy到用戶空間這個過程還是阻塞的。
它的缺點是發了很多次的系統調用,用戶進程需要不斷地去詢問內核,說,內核你的數據準備好了嗎,這是它的缺點
IO多路複用
IO multiplexing就是我們說的select,poll,epoll,select/epoll的好處就在於單個process就可以同時處理多個網絡連接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。
當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。
說明:IO多路複用中包括 select、pool、epoll,這些都屬於同步,還不屬於異步。
select
select:select最早於1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。也就是說,假設select監視100個連接,這100個連接其中一個有數據了,到底是誰,不知道,只自己循環一遍
poll
poll:它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。他是一個過渡。
epoll
epoll:epoll是Linux2.6以上的內核才支持的,window不支持epoll,只支持select,epoll它使用了事件的就緒通知方式,只去循環活躍的連接,不像select一樣,所有監視的連接都去循環一遍,所以效率很高,
nginx介紹
nginx是高性能的web服務器,相比Apache,它可以支持更大的併發,資源佔用小,支持異步模型epoll,因爲他的性能,經常用做負載均衡,反向代理服務器。
nginx的請求處理流程
由上圖,我們的請求流量主要分爲三類,WEB,EMAIL和TCP,在nginx中有三個狀態機,傳輸層狀態機,HTTP狀態機,以及MAIL狀態機,因爲 Nginx 核心的這個大綠色的框他是用非阻塞的事件驅動處理引擎就是用我們所熟知的 epoll,那麼一旦我們使用這種異步處理引擎以後,通常都是需要用狀態機來把這個請求正確的識別和處理。
基於這樣的一種事件狀態處理機,我們在解析出請求需要訪問靜態資源的時候,我們看到走左下方的這個箭頭,那麼它就找到了靜態資源,如果我們去做反向代理的時候呢,那麼對反向代理的內容,我可以做磁盤緩存,緩存到磁盤上,也在下面左下方這條線,但是我們在處理靜態資源的時候,會有一個問題就是當整個內存已經不足以完全的緩存所有的文件和信息的時候,那麼像 send File 這樣的調用或者 AIO 會退化成阻塞的磁盤調用,所以在這裏我們需要有一個線程池來處理,對於每一個處理完成的請求呢,我們會進入 access 日誌或 error 日誌。
那麼這裏也是進入了磁盤中的,當然我們可以通過 syslog 協議把它進入到遠程的機器上,那麼更多的時候我們的 Nginx 是作爲負載均衡或者反向代理來使用的,就是我們可以把請求通過協議級(HTTP,Mail 及 stream(TCP))傳輸到後面的服務器,也可以通過例如應用層的一些協議(FastCGI、uWSGI、SCGI、memcached)代理到相應的應用服務器。以上就是 Nginx 的請求處理流程。
Apache與nginx的區別
參考:
Apache與nginx有什麼區別
apache與nginx區別總結
Apache和Nginx最核心的區別在於 apache 是同步多進程模型,一個連接對應一個進程;而 nginx 是異步的,多個連接(萬級別)可以對應一個進程,
Apache:
缺點:資源佔用率高,併發性能差
優點:
- 穩定,Apache最主要的優點是穩定,bug少,它出現要比nginx早。
- rewrite功能強大,Apache的rewrite功能比nginx強大。
- 模塊多
原理:使用阻塞+多進程
nginx
優點:
- 抗併發能力強,但是沒有LVS強
- 模塊豐富
- 可以做http反向代理及加速緩存,即負載均衡
- 支持異步IO模型epoll
原理:使用IO多路複用,Epoll
爲什麼nginx支持高併發
相比Apache,nginx的併發能力非常強,Apache一般超過一萬個併發,就很難抗的住,但是nginx一般情況下,500萬併發是
nginx實戰
Nginx的組成部分
- nginx二進制可執行文件,由各個模塊的源碼編譯出來的文件,nginx最好使用源碼安裝,這樣我們可以選擇使用什麼模塊
nginx的語法配置規則
1.nginx配置文件的語法規則
- nginx的配置文件由指令和指令塊組成
- 指令塊通過{}把指令組織起來
- 指令之間以分號分隔
- nginx的配置文件可以使用include指令把其他配置文件引用進來,組合多個配置文件來提高可維護性
- nginx的變量使用$,註釋使用#
- 部分指令的參數支持正則表達式
2.nginx的指令塊
根據下面nginx的配製文件代碼,可以很明顯的分爲三個部分
nginx的四個指令塊
- http
- server
- location
- upstream
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
使用信號管理nginx父子進程
- Master進程是主進程,不用來處理連接,它主要做一些特權級別的操作,比如重載配置文件,管理worker進程等等,它通過CHLD信號監控worker進程,因爲worker進程終止的時候,會向它的父進程也就是master進程發送CHLD信號,如果worker進程有一些模塊出現了bug,導致worker進程終止掉,那麼master進程可以立刻通過CHLD發現這個事件,然後重新把worker進程拉起,
- master進程可以接受的信號有TERM,INT(立刻關閉),QUIT優雅關閉,HUP重新加載配置文件,USR1重新打開配置文件,做日誌文件切割,USR2和WINCH只能用kill發送信號,沒有對應的命令行,這兩個是專門針對做熱部署的
- worker進程也可以接受信號,一般情況下,我們不直接向它發送信號,worker進程接收的master進程的信號。
nginx重載配置文件文件的真相
反向代理
反向代理的配置是在location區塊裏面使用proxy_pass
指令進行配置。
我這裏有兩臺虛擬機做實驗,第一臺IP爲10.10.31.8,配置了Apache服務。
然後現在我使用另一臺虛擬機(IP爲10.10.31.10),配置nginx服務,使用proxy_pass
指令,當我們訪問10.10.31.10的時候,把請求轉給10.10.31.8
進行訪問測試,訪問10.10.31.10跳轉到了10.10.31.8,實現了反向代理的功能
反向代理實驗二
負載均衡
因爲我的配置文件配置了比較多東西,所以直接貼核心代碼給大家。
負載均衡配置是在http塊裏面使用upstream
指令,在upstream
指令後面寫我們這個負載均衡集羣的名字
# 負載均衡配置
upstream myserver{
ip_hash;
server 10.10.31.28:80 weight=1;
server 10.10.31.26:80 weight=2;
}
然後在server塊裏面使用upstream定義的集羣的名字,注意這裏要對應,不然會訪問不到的。
location / {
proxy_pass http://myserver;
proxy_connect_timeout 10;
動靜分離
動靜分離指的是將動態請求和靜態請求分開,一般有兩種方法配置,一種是把靜態文件獨立成單獨的域名,放在獨立的服務器上,(比較推崇的方式),另一種是動態和靜態內容在同一個服務器上,通過location指定不同的後綴名來分開,下面配置的重點是增加了一個location配置
location /www/ {
root /data/;
index index.html index.htm;
}
location /image/ {
root /data/;
autoindex on;
}
nginx原理與優化參數
nginx的master-workers機制的好處
首先,對於每個 worker 進程來說,獨立的進程,不需要加鎖,所以省掉了鎖帶來的開銷,同時在編程以及問題查找時,也會方便很多。其次,採用獨立的進程,可以讓互相之間不會
影響,一個進程退出後,其它進程還在工作,服務不會中斷,master 進程則很快啓動新的 worker 進程。當然,worker 進程的異常退出,肯定是程序有 bug 了,異常退出,會導致當前 worker 上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。