select、poll、epoll
在傳入的性能差異是不是體現在,當有新的連接過來,此時需要將新的fd傳入到內核中,但是poll/select需要出入整個數組,而epoll方式只需要出入單個fd?
1. select/poll 的情況
-
它們沒有內核中“長期保存的集合”。
-
每次調用
select()
或poll()
,你都要把 整個數組/集合(所有要監控的 fd)重新傳給內核。 -
不管你只是新增了 1 個連接,還是 100 個連接,調用時都要把全部 fd 拷貝一次。
- 例如原來監控 1000 個 fd,現在來了一個新連接,數組變成 1001 個,調用時需要把這 1001 個 fd 整個傳到內核。
- 所以每次調用開銷是 O(N),與當前總 fd 數成正比。
2. epoll 的情況
-
內核里有一個持久化的數據結構(紅黑樹 + 就緒鏈表)來保存“我關心的 fd”。
-
新連接到來時,只需 調用一次
epoll_ctl(ADD, fd)
把這個單個 fd 注冊進去,內核集合就更新了。 -
后續的
epoll_wait
不需要再傳全集合,只返回就緒 fd。- 所以新增 fd 的代價是 O(1)(單次 syscall,處理一個 fd),而等待就緒事件的代價是 O(#ready)。
3. 所以性能差異體現在
- select/poll:每次 wait 都要把“全量數組”搬到內核,新增一個 fd 后,每次 wait 的拷貝量都會增加。
- epoll:新增 fd 時只需要告訴內核“再加一個”,以后 wait 的時候不用每次重新拷貝整個集合。
一句話總結
是的,差異就在于:
- select/poll → 新增一個連接后,后續每次調用都要傳 整個 fd 集合。
- epoll → 新增一個連接時,只需要傳 那個新 fd 一次,之后 wait 再也不用傳全集合。
使用水平觸發模式時,當被監控的 Socket 上有可讀事件發生時,服務器端不斷地從 epoll_wait 中蘇醒,直到內核緩沖區數據被 read 函數讀完才結束——這里蘇醒是什么意思
這個“蘇醒”其實是個形象的說法,指的是:
線程在調用 epoll_wait
時會進入阻塞睡眠狀態,當監控的 socket 上發生了指定事件(比如可讀)時,內核會把這個阻塞的線程喚醒,讓它從 epoll_wait
返回。
更精確的過程
-
你調用:
int n = epoll_wait(epfd, events, maxevents, timeout);
- 如果此時沒有任何 fd 就緒,當前線程就會進入 睡眠狀態,掛起在內核的等待隊列里。
-
當某個被監控的 fd 上發生事件(例如 socket 收到數據,可讀了):
- 內核把這個事件加入 epoll 的“就緒隊列”。
- 內核檢查有線程在
epoll_wait
上阻塞,就會喚醒這些線程。
-
線程被喚醒后,
epoll_wait
返回,你得到就緒事件。- 如果是 水平觸發(LT),只要緩沖區里還有沒讀完的數據,fd 就一直處于就緒狀態,下一次調用
epoll_wait
又會馬上返回(繼續“蘇醒”)。 - 只有當你把數據讀干凈(read 返回
EAGAIN
),fd 才會變成未就緒,下次epoll_wait
才會重新睡眠。
- 如果是 水平觸發(LT),只要緩沖區里還有沒讀完的數據,fd 就一直處于就緒狀態,下一次調用
“蘇醒”的本質
- 內核通過 等待隊列(wait queue) 機制把阻塞的線程放眠。
- 當事件發生時,內核調用 wake_up 把線程從等待隊列移出,讓調度器把它重新放到可運行隊列里。
- 線程因此“蘇醒”,從
epoll_wait
返回。
用白話類比
- 睡眠:線程打盹,等著內核叫醒。
- 蘇醒:內核發現“喂,socket 來數據了!”,拍醒線程說“起來干活!”。
- 水平觸發下的不斷蘇醒:只要桌子上還有沒吃完的飯(緩沖區里有數據),內核就會不停叫醒你;只有你把飯吃光(讀干凈),它才不再打擾你。
那epoll和Reactor之間的關系是什么?是不是Reactor基于epoll實現的
你問得很好 👍,這是很多人一開始都會混淆的地方。
1. epoll 是“機制”,Reactor 是“模式”
-
epoll:是 Linux 內核提供的 I/O 多路復用接口,是一種“系統調用機制”。它的職責就是:
- 高效地監控一批 fd(socket)是否就緒;
- 把就緒事件通知給用戶態程序。
👉 屬于 操作系統層面的能力。
-
Reactor:是一種 事件驅動編程模型/設計模式。它描述的是如何組織程序結構:
- 一個線程等待事件(
epoll_wait
等),當有事件到來時,分發給對應的“事件處理器(Handler)”來處理。
👉 屬于 應用層面的架構模式。
- 一個線程等待事件(
2. 關系
-
Reactor 模式通常用 epoll 來實現(在 Linux 下)。
-
但 Reactor 并不依賴 epoll,它也可以用 select、poll、kqueue(BSD)、IOCP(Windows) 等機制來實現。
-
換句話說:
- epoll = 內核提供的工具。
- Reactor = 用這個工具組織起來的一種設計模式。
3. 舉例
比如一個典型的 Reactor 服務器流程:
while (true) {int n = epoll_wait(epfd, events, maxevents, -1); // 等待事件for (i = 0; i < n; i++) {if (events[i].data.fd == listen_fd) {// 有新連接 => 交給 Acceptoraccept_new_connection();} else {// 有數據可讀/可寫 => 交給對應的 Handlerhandler(events[i].data.fd);}}
}
這里:
epoll_wait
就是 內核機制。- 整個 while 循環里“等待事件-分發-處理”的結構,就是 Reactor 模式。
4. 總結
- epoll 是 操作系統提供的事件通知機制。
- Reactor 是 一種事件驅動的編程模式。
- 在 Linux 里,Reactor 通常是 基于 epoll 來實現的,但在其他系統上也可能基于 poll、kqueue、IOCP 等。
哪些框架是基于Netty?
好問題 👍。Netty 本身就是一個 基于 Reactor 模式、封裝了 Java NIO/epoll 的高性能網絡通信框架。很多知名框架和中間件都直接或間接使用了 Netty 來處理底層網絡通信。
常見基于 Netty 的框架/中間件
RPC / 微服務框架
- Dubbo(阿里開源的 RPC 框架,核心通信基于 Netty)
- gRPC-Java(Google 的 RPC 框架,Java 實現底層默認用 Netty)
- Motan(新浪微博開源的 RPC 框架)
- SOFARPC(螞蟻金服開源的 RPC 框架,默認基于 Netty)
消息中間件
- RocketMQ(阿里開源的分布式消息隊列,通信層用 Netty)
- Kafka(部分組件)(核心基于 NIO,部分工具/生態里使用 Netty)
- ActiveMQ Artemis(底層網絡層實現可選 Netty)
數據庫相關
- Redis 的部分 Java 客戶端(如 Redisson 使用 Netty 管理連接池和通信)
- Cassandra Java 驅動(使用 Netty 處理連接)
- Elasticsearch Java 客戶端(低層 IO 使用 Netty)
網關 / 代理
- Zuul 2(Netflix 開源 API Gateway,底層通信是 Netty)
- Spring Cloud Gateway(基于 Spring WebFlux,而 WebFlux 默認基于 Reactor-Netty)
- gRPC-Gateway Java 生態實現
Web 服務器 / HTTP 框架
- Reactor Netty(Spring 官方提供的 Netty 封裝,Spring WebFlux 默認用它)
- Vert.x(異步應用框架,默認底層用 Netty)
- Play Framework(早期版本)(部分基于 Netty 的異步處理)
其他
- Finagle(Twitter 開源的異步 RPC 框架,支持多協議,底層 Netty 實現之一)
- HiveMQ(MQTT broker,基于 Netty 實現高并發連接)
- Elasticsearch Transport 層(基于 Netty 處理節點間通信)
一句話總結
Netty = Java 世界的“網絡通信基石”。
凡是需要 高并發網絡通信 的 Java 框架/中間件,大概率都直接或間接用了 Netty,比如 Dubbo、RocketMQ、Spring Cloud Gateway、Elasticsearch、gRPC-Java。