知识卡片
Nginx线程池:把会阻塞事件循环的同步磁盘IO挪到独立线程池
内容
Nginx本身采用事件驱动模型,多进程部署下每个worker进程通常只有一个线程,靠事件循环支撑同时响应成千上万个并发连接。但当服务的文件总量远超机器内存、且这些文件又都是热点文件时,问题出现了:Linux下常规的文件访问会走操作系统的页缓存(用来加速热点文件的磁盘访问),一旦请求的数据没能命中页缓存,这次磁盘IO操作本身是同步阻塞的,而Nginx的事件循环模型正是靠一个线程处理所有连接的事件,一旦这个线程被某次同步磁盘IO操作卡住,同一个worker上其他所有连接的处理都会跟着被拖慢,表现为连接超时或建立缓慢。Nginx 1.7.12⁄1.8版本引入线程池来缓解这个问题:主线程遇到需要发起文件系统操作时,不再自己同步等待,而是把这个文件操作封装成一个作业丢进线程池的作业队列,由线程池里的线程去执行,执行完把结果放回队列,主线程只需要处理结果、不会被磁盘IO本身阻塞住,从而让事件循环重新变得非阻塞。这个案例给出一条对任何事件驱动/单线程异步模型都适用的重要补充:单线程加异步模型本身依赖”所有操作都可以是非阻塞的”这个前提,一旦遇到操作系统层面确实是同步阻塞的操作(如某些场景下的文件IO),就必须把这类操作单独挪到线程池等待,而不能假设整个系统所有环节都能天然异步化,[[单线程加异步模型:用架构简化换取团队实际可维护性]]这类设计仍然需要为无法回避的阻塞点预留线程池这个安全阀。
参考来源
- 位置:《高可用架构(第1卷)》第7章《安全与网络》"7.3 CDN对流媒体和应用分发的支持及优化"节,"7.3.4 内容分发系统的问题和应对思路","3.Nginx在Linux上的性能优化"(源文件:_epub-src/OEBPS/Text/Chapter7_3_5.xhtml)
- 结论依据:原文说明"如果服务文件的存储大小比机器内存大很多,并且这些文件都是热点文件时,连接Nginx服务器可能会引起连接超时或链接建立过慢的问题……如果使用第2类模式(直接访问模式)……就会导致事件模型被文件操作卡住,无法正常地响应连接……Nginx通过引入thread pool来缓解上面的问题……主线程不会再被阻塞住,因此建立连接将不会被卡住",直接支撑本卡片结论。
- 原始内容:当Nginx运行在Linux系统上时,如果服务文件的存储大小比机器内存大很多,并且这些文件都是热点文件时,连接Nginx服务器可能会引起连接超时或链接建立过慢的问题……Nginx通过引入thread pool来缓解上面的问题……通过这个操作,主线程不会再被阻塞住,因此建立连接将不会被卡住。