鍍金池/ 教程/ Java/ Socket 編程發(fā)展
定時任務(wù)
函數(shù)的參數(shù)
超時
一個 openresty 內(nèi)存“泄漏”問題
獲取 uri 參數(shù)
局部變量
sleep
灰度發(fā)布
TIME_WAIT
代碼覆蓋率
連接池
CentOS 平臺安裝
稀疏數(shù)組
如何只啟動一個 timer 工作?
變量的共享范圍
break,return 關(guān)鍵字
Nginx
SQL 注入
如何引用第三方 resty 庫
不同階段共享變量
獲取請求 body
動態(tài)生成的 lua-resty-redis 模塊方法
動態(tài)加載證書和 OCSP stapling
repeat 控制結(jié)構(gòu)
編碼為 array 還是 object
Nginx 靜態(tài)文件服務(wù)
執(zhí)行階段概念
Lua 函數(shù)
日期時間函數(shù)
健康監(jiān)測
與其他 location 配合
for 控制結(jié)構(gòu)
函數(shù)定義
HTTPS 時代
點號與冒號操作符的區(qū)別
String 庫
文件操作
OpenResty 最佳實踐
<code>ngx.shared.DICT</code> 非隊列性質(zhì)
使用動態(tài) DNS 來完成 HTTP 請求
代碼規(guī)范
什么是 JIT?
Windows 平臺安裝
正確的記錄日志
LuaNginxModule
不用標(biāo)準(zhǔn)庫
C10K 編程
控制結(jié)構(gòu)
請求中斷后的處理
Lua 環(huán)境搭建
Test::Nginx 能指定現(xiàn)成的 nginx.conf,而不是自動生成一個嗎
Lua 基礎(chǔ)數(shù)據(jù)類型
動態(tài)限速
PostgresNginxModule
簡單API Server框架
API 測試
location 匹配規(guī)則
虛變量
單元測試
防止 SQL 注入
select + set_keepalive 組合操作引起的數(shù)據(jù)讀寫錯誤
阻塞操作
全動態(tài)函數(shù)調(diào)用
Web 服務(wù)
典型應(yīng)用場景
Nginx 新手起步
TLS session resumption
輸出響應(yīng)體
調(diào)用代碼前先定義函數(shù)
module 是邪惡的
怎樣理解 cosocket
模塊
Socket 編程發(fā)展
如何對 Nginx Lua module 添加新 api
如何在后臺開啟輕量級線程完成定時任務(wù)?
如何定位問題
table 庫
json 解析的異常捕獲
如何安裝火焰圖生成工具
lua 中如何 continue
if 是邪惡的
為什么我們的域名不能被解析
抵制使用 module() 定義模塊
測試
body 在 location 中的傳遞
Lua 入門
子查詢
pipeline 壓縮請求數(shù)量
如何發(fā)起新 HTTP 請求
Lua 簡介
緩存失效風(fēng)暴
Ubuntu 平臺安裝
日志輸出
緩存
Lua 面向?qū)ο缶幊?/span>
Nginx 陷阱和常見錯誤
Redis 接口的二次封裝(發(fā)布訂閱)
日志
訪問有授權(quán)驗證的 Redis
正則表達式
lock
熱裝載代碼
調(diào)用 FFI 出現(xiàn) &quot;table overflow&quot;
數(shù)據(jù)合法性檢測
禁止某些終端訪問
控制結(jié)構(gòu) if-else
調(diào)試
與 Docker 使用的網(wǎng)絡(luò)瓶頸
PostgresNginxModule 模塊的調(diào)用方式
用 do-end 整理你的代碼
FFI
什么時候使用
簡介
環(huán)境搭建
Mac OS X 平臺安裝
火焰圖
負載均衡
while 型控制結(jié)構(gòu)
如何定位 openresty 崩潰 bug
使用 Nginx 內(nèi)置綁定變量
判斷數(shù)組大小
請求返回后繼續(xù)執(zhí)行
Redis 接口的二次封裝
KeepAlive
反向代理
協(xié)議無痛升級
數(shù)學(xué)庫
元表
Vanilla 介紹
HelloWorld
LuaCjsonLibrary
持續(xù)集成
代碼靜態(tài)分析
網(wǎng)上有大量對 Lua 調(diào)優(yōu)的推薦,我們應(yīng)該如何看待?
script 壓縮復(fù)雜請求
非空判斷
性能測試
函數(shù)返回值
API 的設(shè)計
kong 介紹
表達式
不支持事務(wù)
LuaRestyDNSLibrary 簡介

Socket 編程發(fā)展

Linux Socket 編程領(lǐng)域,為了處理大量連接請求場景,需要使用非阻塞 I/O 和復(fù)用。select、poll 和 epoll 是 Linux API 提供的 I/O 復(fù)用方式,自從 Linux 2.6 中加入了 epoll 之后,高性能服務(wù)器領(lǐng)域得到廣泛的應(yīng)用,現(xiàn)在比較出名的 Nginx 就是使用 epoll 來實現(xiàn) I/O 復(fù)用支持高并發(fā),目前在高并發(fā)的場景下,Nginx 越來越收到歡迎。

據(jù) w3techs 在 2015 年 8 月 10 日的統(tǒng)計數(shù)據(jù)表明,在全球 Top 1000 的網(wǎng)站中,有 43.7% 的網(wǎng)站在使用 Nginx,這使得 Nginx 超越了 Apache,成為了高流量網(wǎng)站最信任的 Web 服務(wù)器足足有兩年時間。已經(jīng)確定在使用 Nginx 的站點有:Wikipedia,WordPress,Reddit,Tumblr,Pinterest,Dropbox,Slideshare,Stackexchange 等,可以持續(xù)羅列好幾個小時,他們太多了。

下圖是統(tǒng)計數(shù)據(jù):

http://wiki.jikexueyuan.com/project/openresty/images/nginx.png" alt="" />

select 模型

下面是 select 函數(shù)接口:

int select (int n, fd_set *readfds, fd_set *writefds,
        fd_set *exceptfds, struct timeval *timeout);

select 函數(shù)監(jiān)視的文件描述符分 3 類,分別是 writefds、readfds 和 exceptfds。調(diào)用后 select 函數(shù)會阻塞,直到有描述符就緒(有數(shù)據(jù) 可讀、可寫、或者有 except),或者超時(timeout 指定等待時間,如果立即返回設(shè)為 null 即可)。當(dāng) select 函數(shù)返回后,通過遍歷 fd_set,來找到就緒的描述符。

select 目前幾乎在所有的平臺上支持,其良好跨平臺支持是它的一大優(yōu)點。select 的一個缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在 Linux 上一般為 1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會造成效率的降低。

poll 模型

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

不同于 select 使用三個位圖來表示三個 fdset 的方式,poll 使用一個 pollfd 的指針實現(xiàn)。

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};

pollfd 結(jié)構(gòu)包含了要監(jiān)視的 event 和發(fā)生的 event,不再使用 select “參數(shù)-值”傳遞的方式。同時,pollfd 并沒有最大數(shù)量限制(但是數(shù)量過大后性能也是會下降)。和 select 函數(shù)一樣,poll 返回后,需要輪詢 pollfd 來獲取就緒的描述符。

從上面看,select 和 poll 都需要在返回后,通過遍歷文件描述符來獲取已經(jīng)就緒的 socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會線性下降。

epoll 模型

epoll 的接口如下:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
            typedef union epoll_data {
                void *ptr;
                int fd;
                __uint32_t u32;
                __uint64_t u64;
            } epoll_data_t;

            struct epoll_event {
                __uint32_t events;      /* Epoll events */
                epoll_data_t data;      /* User data variable */
            };

int epoll_wait(int epfd, struct epoll_event * events,
                int maxevents, int timeout);

主要是 epoll_create,epoll_ctl 和 epoll_wait 三個函數(shù)。epoll_create 函數(shù)創(chuàng)建 epoll 文件描述符,參數(shù) size 并不是限制了 epoll 所能監(jiān)聽的描述符最大個數(shù),只是對內(nèi)核初始分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一個建議。epoll_ctl 完成對指定描述符 fd 執(zhí)行 op 操作控制,event 是與 fd 關(guān)聯(lián)的監(jiān)聽事件。op 操作有三種:添加 EPOLL_CTL_ADD,刪除 EPOLL_CTL_DEL,修改 EPOLL_CTL_MOD。分別添加、刪除和修改對 fd 的監(jiān)聽事件。epoll_wait 等待 epfd 上的 IO 事件,最多返回 maxevents 個事件。

在 select/poll 中,進程只有在調(diào)用一定的方法后,內(nèi)核才對所有監(jiān)視的文件描述符進行掃描,而 epoll 事先通過 epoll_ctl() 來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內(nèi)核會采用類似 callback 的回調(diào)機制,迅速激活這個文件描述符,當(dāng)進程調(diào)用 epoll_wait 時便得到通知。

epoll 的優(yōu)點主要是一下幾個方面:

  1. 監(jiān)視的描述符數(shù)量不受限制,它所支持的 fd 上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠大于 2048, 舉個例子, 在 1GB 內(nèi)存的機器上大約是 10 萬左右,具體數(shù)目可以 cat /proc/sys/fs/file-max 察看, 一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。select 的最大缺點就是進程打開的 fd 是有數(shù)量限制的。這對于連接數(shù)量比較大的服務(wù)器來說根本不能滿足。雖然也可以選擇多進程的解決方案( Apache 就是這樣實現(xiàn)的),不過雖然 linux 上面創(chuàng)建進程的代價比較小,但仍舊是不可忽視的,加上進程間數(shù)據(jù)同步遠比不上線程間同步的高效,所以也不是一種完美的方案。
  2. IO 的效率不會隨著監(jiān)視 fd 的數(shù)量的增長而下降。epoll 不同于 select 和 poll 輪詢的方式,而是通過每個 fd 定義的回調(diào)函數(shù)來實現(xiàn)的。只有就緒的 fd 才會執(zhí)行回調(diào)函數(shù)。
  3. 支持水平觸發(fā)和邊沿觸發(fā)兩種模式:
    • 水平觸發(fā)模式,文件描述符狀態(tài)發(fā)生變化后,如果沒有采取行動,它將后面反復(fù)通知,這種情況下編程相對簡單,libevent 等開源庫很多都是使用的這種模式。
    • 邊沿觸發(fā)模式,只告訴進程哪些文件描述符剛剛變?yōu)榫途w狀態(tài),只說一遍,如果沒有采取行動,那么它將不會再次告知。理論上邊緣觸發(fā)的性能要更高一些,但是代碼實現(xiàn)相當(dāng)復(fù)雜(Nginx 使用的邊緣觸發(fā))。
  4. mmap 加速內(nèi)核與用戶空間的信息傳遞。epoll 是通過內(nèi)核與用戶空間 mmap 同一塊內(nèi)存,避免了無謂的內(nèi)存拷貝。
上一篇:TIME_WAIT下一篇:變量的共享范圍