后端面试问 epoll,很多回答会直接说“比 select 快,适合高并发”。这句话方向没错,但太粗。面试官继续问为什么快、快在哪里、用了 epoll 服务就一定能扛住并发吗,答案就不能停在口号上。
epoll 的核心价值不是让业务代码执行得更快,而是在大量连接里减少无意义等待和扫描。它解决的是“哪些连接已经准备好读写”的发现成本,不解决 SQL 慢、业务锁竞争、下游超时、序列化过重这些应用层问题。
select 和 poll 的问题在哪里
select/poll 的思路比较直观:把一批文件描述符交给内核,内核检查哪些就绪,再把结果返回。连接数量少时问题不大,连接数量大但真正活跃的连接很少时,就会出现明显浪费:每次都要处理一大批并没有事件的连接。
epoll 把关注的文件描述符注册到内核维护的结构里,事件发生时再把就绪项放到可取出的队列中。应用调用 epoll_wait 时,拿到的是已经就绪的一批事件。严格说它不是任何场景都“常数复杂度”,但在大量长连接、少量活跃的典型网络服务里,能显著减少无效扫描。
就绪通知不等于数据已经全部处理完
epoll 告诉你的是“这个连接现在可以读或可以写”,不是告诉你“一次就能读完整个请求”。所以高并发服务通常要配合非阻塞 IO。读的时候循环读到 EAGAIN,写的时候处理短写和缓冲区,不能假设一次 read 或 write 就结束。
这也是边缘触发和水平触发容易被问的原因。水平触发下,只要条件还满足,后续仍会通知;边缘触发更依赖你一次把状态处理干净,否则可能因为没有新的边缘变化而卡住。面试里不要只说 ET 更快,应该说 ET 更考验非阻塞读写和缓冲区处理能力。
Reactor 线程模型才连接到项目
项目里常见的 Netty、Nginx 或自研网络框架,通常不是直接把 epoll 暴露给业务,而是通过 Reactor 模型组织事件循环。一个或多个线程负责 accept 和 IO 事件,业务处理可以在事件循环内完成,也可以投递到业务线程池。
这里有一个非常实际的边界:事件循环线程不能被慢业务阻塞。你在 IO 线程里做慢查询、远程调用、大文件解析,epoll 再高效也没用,因为事件被取出来后没人及时处理。高并发系统真正要设计的是 IO 线程、业务线程、队列长度、超时和降级。
高并发不是只调一个参数
连接数上来后,除了 epoll,还要看文件描述符限制、连接超时、半连接队列、内核缓冲区、应用线程池、数据库连接池和下游限流。很多线上事故不是“epoll 不够快”,而是业务线程池排队、连接池耗尽、下游慢导致请求堆积。
面试里如果被问“怎么设计高并发服务器”,可以先分层:网络层负责连接和事件通知,协议层负责解析与编码,业务层控制执行时间和资源隔离,存储层控制连接池和慢查询,观测层看队列、延迟和错误率。这样 epoll 才会落在正确的位置。
一个更稳的总结
epoll 是高并发网络服务的底层能力之一,它让程序更高效地等待大量连接上的就绪事件。但它不是性能万能药。只有和非阻塞 IO、合理线程模型、背压、超时、限流、连接池治理一起设计,才能真正支撑后端服务的稳定并发。