- 后端
- 网络
【免费下载链接】puma
A Ruby/Rack web server built for parallelism
Puma 作为 Ruby/Rack 服务器通常部署在 Nginx 之后,由 Nginx 承担 80/443 端口监听、静态文件服务与反向代理职责。本文基于仓库中的 docs/nginx.md 官方配置示例,逐段拆解一套经典且可直接复用的 Nginx 配置:从unix://socket upstream、keepalive 超时调优,到 Rack 页面缓存重写、维护页切换与错误页兜底,并结合 lib/puma/dsl.rb 等源码说明 Puma 侧如何与之对齐,帮助读者完整掌握“Nginx + Puma”生产级部署方案。
一、整体架构:Nginx 在前,Puma upstream 在后
典型的生产部署形态是:Nginx 监听公网 80 端口并持有静态文件目录(/myapp/public),所有未命中静态资源的动态请求被proxy_pass转发到 Puma upstream。Puma 默认监听 TCP 端口(如tcp://0.0.0.0:9292),但官方示例推荐使用Unix socket(unix:///myapp/tmp/puma.sock)作为 upstream,因为:
- 仅限本机进程间通信,无需经过网卡与 TCP 协议栈,延迟更低;
- 不存在暴露 TCP 端口带来的防火墙/暴露面问题;
- Nginx 与 Puma 同机部署时是最常规、性能最优的组合方式。
仓库源码对 Unix socket 提供了完整支持:lib/puma/dsl.rb 中的bind方法明确声明只接受tcp://、unix://、ssl://三种协议,并支持通过 URL query 参数配置 socket 的backlog(默认 1024)与umask(socket 权限)。测试用例 test/test_unix_socket.rb 也验证了通过UNIXSocket直接向 Unix socket 发起 HTTP 请求的完整链路。
二、Puma 侧准备:绑定 Unix socket
要让上文架构成立,首先需要让 Puma 监听在 Unix socket 上。在 Puma 配置文件中(例如config/puma.rb)写入:
# config/puma.rb # 绑定 Unix socket,路径必须与 nginx 的 upstream server 一致 bind 'unix:///myapp/tmp/puma.sock' # 可选:通过 query 参数调整 socket 行为 # bind 'unix:///myapp/tmp/puma.sock?backlog=512' # 设置 socket 队列深度,默认 1024 # bind 'unix:///myapp/tmp/puma.sock?umask=0111' # 限制 socket 文件权限 # 生产环境常用补充配置 environment 'production' threads 5, 5 # 每进程线程数,官方建议 5(见 deployment 文档) workers 4 # 进程数,建议与 CPU 核数匹配 pidfile '/myapp/tmp/puma.pid' state_path '/myapp/tmp/puma.state'对应源码细节(lib/puma/dsl.rb):
def bind(url) @options[:binds] ||= [] @options[:binds] << url endbind可被多次调用且不会互相覆盖,因此 Puma 可以同时绑定 Unix socket 与 TCP 端口。workers传入:auto时(需concurrent-rubygem)会依据Concurrent.available_processor_count自动匹配机器 CPU 核数(lib/puma/dsl.rb);threads min, max会校验 min ≤ max 且 max ≥ 1(lib/puma/dsl.rb)。
需要特别注意:Puma 必须先于 Nginx 启动并成功创建 socket 文件,否则 Nginx 的upstream会因找不到 socket 而报connect() to unix://... failed。建议由 systemd 等进程管理器保证 Puma 常驻,参见 docs/systemd.md 与 docs/deployment.md 中关于进程监控的论述。
三、Nginx 完整配置示例(官方原文)
以下是 docs/nginx.md 提供的完整示例,是一个常见的 upstream 部署模板,源自社区 Capistrano 部署经验:
upstream myapp { server unix:///myapp/tmp/puma.sock; } server { listen 80; server_name myapp.com; # ~2 seconds is often enough for most folks to parse HTML/CSS and # retrieve needed images/icons/frames, connections are cheap in # nginx so increasing this is generally safe... keepalive_timeout 5; # path for static files root /myapp/public; access_log /myapp/log/nginx.access.log; error_log /myapp/log/nginx.error.log info; # this rewrites all the requests to the maintenance.html # page if it exists in the doc root. This is for capistrano's # disable web task if (-f $document_root/maintenance.html) { rewrite ^(.*)$ /maintenance.html last; break; } location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; # If the file exists as a static file serve it directly without # running all the other rewrite tests on it if (-f $request_filename) { break; } # check for index.html for directory index # if it's there on the filesystem then rewrite # the url to add /index.html to the end of it # and then break to send it to the next config rules. if (-f $request_filename/index.html) { rewrite (.*) $1/index.html break; } # this is the meat of the rack page caching config # it adds .html to the end of the url and then checks # the filesystem for that file. If it exists, then we # rewrite the url to have explicit .html on the end # and then send it on its way to the next config rule. # if there is no file on the fs then it sets all the # necessary headers and proxies to our upstream pumas if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://myapp; break; } } # Now this supposedly should work as it gets the filenames with querystrings that Rails provides. # BUT there's a chance it could break the ajax calls. location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; } # Error pages # error_page 500 502 503 504 /500.html; location = /500.html { root /myapp/current/public; } }下文逐段解释每部分的作用与调优要点。
四、upstream 与 keepalive_timeout
upstream myapp { server unix:///myapp/tmp/puma.sock; } server { ... keepalive_timeout 5; ... }upstream myapp定义了一个名为myapp的上游服务器组,成员是本机 Puma 的 Unix socket。若想扩展为多实例负载均衡,可在其中追加多个server条目。keepalive_timeout 5控制 Nginx 与客户端之间空闲连接的超时时间。官方注释指出:约 2 秒通常足够客户端解析 HTML/CSS 并获取所需的图片/图标/框架资源;由于 Nginx 连接开销很低,适当调大这个值一般是安全的。
这里需要把“Nginx ↔ 客户端”与“Nginx ↔ Puma”两条 keepalive 链路区分开:keepalive_timeout只作用于前者,而 Nginx 到 Puma upstream 的连接池化还需要在upstream块内使用keepalive指令,例如:
upstream myapp { server unix:///myapp/tmp/puma.sock; keepalive 32; # 复用 Nginx 到 Puma 的空闲连接 }Puma 侧对 keepalive(HTTP 持久连接)的支持是内置的:默认persistent_timeout为 65 秒(见 lib/puma/dsl.rb 附近源码注释),即客户端空闲连接在 65 秒内可被复用。仓库 docs/7.0-Upgrade.md 也特别提到 Puma 7 对 keepalive 重流量场景的尾延迟(tail latency)做了专门优化,因此长连接部署在 Puma 上是受到官方重视与支持的场景。
五、静态文件服务与 Rack 页面缓存
# path for static files root /myapp/public; ... location / { ... # 静态文件存在则直接返回,不再执行后续 rewrite if (-f $request_filename) { break; } # 目录索引:存在 index.html 则重写 URL 追加 /index.html if (-f $request_filename/index.html) { rewrite (.*) $1/index.html break; } # Rack 页面缓存:把 URL 加上 .html 后缀再检查文件系统 if (-f $request_filename.html) { rewrite (.*) $1.html break; } # 文件系统上不存在对应文件,则带齐转发头交给上游 Puma if (!-f $request_filename) { proxy_pass http://myapp; break; } }这是整套配置中最核心的逻辑,它实现了一个完整的**“静态优先、动态兜底”**请求处理流程:
root /myapp/public声明静态文件根目录,动态应用产出的 assets 均发布于此;- 请求路径对应的文件真实存在时(
-f $request_filename),Nginx 直接以静态文件响应,不会进入 Ruby 应用,从而显著减轻 Puma 负载; - 请求路径对应目录下的
index.html存在时,重写为目录索引文件; - Rack 页面缓存(page caching):如果磁盘上存在
$request_filename.html(例如请求/articles/1时存在/myapp/public/articles/1.html),则重写 URL 直接返回缓存的 HTML。这是不少 Rails/Rack 应用常用的全页缓存策略,由 Nginx 在应用层之前完成命中判断,Puma 完全不必参与; - 以上条件全部不满足时,才通过
proxy_pass http://myapp把请求转发给 Puma。
需要注意的是:location /内的if指令在 Nginx 中属于“rewrite 阶段”指令,其执行时机先于内容处理阶段,因此这套写法在官方示例中成立;同时官方注释也提醒,若追加带 querystring 的静态文件规则(见下文),有可能影响 Ajax 请求,改造时需要充分测试。
六、带版本号的静态资源与长缓存
location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; }该正则 location 匹配常见的图片、CSS、JS 静态资源,并允许 URL 后跟形如?1234567890的数字版本号(Rails 的 asset pipeline 即采用这种指纹/版本号策略)。expires max让浏览器对这些资源做最大时长缓存——由于版本号变化时 URL 也随之变化,缓存失效由文件名版本号自动驱动,无需手动清缓存。官方注释再次提醒:这条规则与 Ajax 调用存在潜在冲突风险,引入前应验证前端请求模式。
七、维护页(maintenance.html)切换
# this rewrites all the requests to the maintenance.html # page if it exists in the doc root. This is for capistrano's # disable web task if (-f $document_root/maintenance.html) { rewrite ^(.*)$ /maintenance.html last; break; }这是配合 Capistranodisable web任务实现的维护模式方案:只要在文档根目录放置maintenance.html文件,所有请求都会被重写并返回该静态维护页,应用进入只读的维护状态;删除该文件即自动恢复服务。这种“以文件存在与否作为开关”的做法无需改动 Nginx 配置、无需 reload,非常适合部署脚本触发。
八、错误页兜底
# Error pages # error_page 500 502 503 504 /500.html; location = /500.html { root /myapp/current/public; }当 Puma 返回 5xx 错误时(如 502 Bad Gateway、503 不可用),error_page指令可让 Nginx 返回统一的自定义错误页/500.html。示例中该页面放在/myapp/current/public(Capistrano 部署中的 current 目录)。location = /500.html的精确匹配保证该 location 只处理这一个路径,且其root覆盖外层root指向的目录。需注意示例将该指令注释掉,读者可自行按需开启,并确保错误页文件真实存在于对应目录。
九、传递客户端信息:X-Forwarded-For 与 Puma 侧的配合
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host;X-Forwarded-For记录了原始客户端 IP($proxy_add_x_forwarded_for会自动追加当前连接的对端地址),使后端应用能够识别真实访客 IP 而不是 Nginx 本机地址;Host $host保持原始 Host 头,供 Rails/Rack 应用做基于 host 的路由或 URL 生成。
Puma 源码对这些转发头有完整的消费逻辑:
- lib/puma/commonlogger.rb 的访问日志输出优先使用
env[HTTP_X_FORWARDED_FOR],其次才是env[REMOTE_ADDR],与 lib/puma/error_logger.rb 的错误日志行为一致——也就是说,只要 Nginx 正确设置了X-Forwarded-For,Puma 日志中的客户端地址就是真实访客 IP; - lib/puma/client_env.rb 在处理请求环境变量时对
REMOTE_ADDR的赋值逻辑明确考虑了存在反代(proxy)的场景,源码注释中直接点明应区分“由代理设置、而非客户端伪造”的头部(如X-Forwarded-For)。
此外,如果应用只通过 HTTPS 对外提供服务,应把proxy_set_header X-Forwarded-Proto $scheme;一并加入,并在 Puma 配置中开启相应选项,确保env['rack.url_scheme']正确为https(可参见 lib/puma/dsl.rb 附近关于X-Forwarded-Proto的注释说明)。
十、利用 X-Request-Start 头测量请求排队时间
deployment.md 提供了一个与本文配置直接配套的生产观测技巧:通过让 Nginx 记录它收到请求的精确时间戳,从而量化请求在 Nginx → Puma 之间的排队等待时长。
在 Nginx 侧添加:
proxy_set_header X-Request-Start "${msec}";然后在 Rack 中间件中计算当前时间与X-Request-Start的差值,即可得到请求自进入 Nginx 到被 Puma 线程处理之间的排队时间。该文档还提示可以进一步扣除慢客户端等待时间:env['puma.request_body_wait']记录了 Puma 等待客户端发送请求体的毫秒数,减去它可以得到更精确的“Puma 线程占用”时间。
十一、与容量规划、部署规范的衔接
最后,这套 Nginx 配置落地的效果取决于 Puma 进程/线程规模的合理性,docs/deployment.md 给出如下与 upstream 架构配套的要点:
- single mode 与 cluster mode:
workers 0为单进程模式(适合 JRuby、TruffleRuby 或单核 MRI 环境);workers > 0进入 cluster mode,多核机器应使用 cluster mode 以充分利用 CPU; - worker 与 CPU 核数:建议 CPU 核与 worker 比例约 1:1,
workers :auto会自动按可用核数设置(需concurrent-rubygem); - threads 默认 5:除非应用属于重 I/O 场景(50%+ 时间等待 IO),否则使用默认线程数即可,盲目调高线程会增加延迟与内存占用;
- 常见起步规格:约 80% 的 Puma 应用最终会选择“4 workers × 5 threads、4 vCPU、8GB 内存”的 pod 规模,内存上限一般不建议低于单进程 2GB;
- 重启与进程管理:Puma 5.0 起移除了 daemonize 能力,官方明确建议用 systemd、runit 等进程监控器守护 Puma(详见 docs/systemd.md),无停机重启的机制见 docs/restart.md。
十二、完整落地清单
综合全文,从零搭建“Nginx + Puma”生产环境时可按下述顺序检查:
- Puma 侧:在配置文件中
bind 'unix:///myapp/tmp/puma.sock',并按机器规模设置workers与threads;确保 socket 路径与 Nginx 一致,目录存在且对运行用户可写; - 启动顺序:先启动 Puma(可由 systemd 托管),确认 socket 文件已生成,再启动/重载 Nginx;
- Nginx 侧:将 docs/nginx.md 示例中
myapp、server_name、root、日志与 socket 路径替换为实际值,按需开启error_page、静态资源正则 location 与 upstreamkeepalive; - 验证转发头:在 Rack 应用中确认
X-Forwarded-For、Host(及 HTTPS 场景的X-Forwarded-Proto)正确透传,Puma 访问日志显示真实客户端 IP; - 压测与观测:参考 docs/deployment.md 的
X-Request-Start方案测量排队时间,结合 CPU/内存占用调整 worker 与线程数,避免进程长期满负荷或长期空闲。
完成以上步骤,即得到一个“Nginx 静态资源缓存 + Puma Unix socket 动态处理”的标准生产部署,可直接套用到 Capistrano 或 systemd 部署体系中。
- 后端
- 网络
【免费下载链接】puma
A Ruby/Rack web server built for parallelism
相关推荐
JeeSite Nginx配置:反向代理与缓存
JeeSite Nginx配置:反向代理与缓存 前言:为什么需要Nginx配置? 在企业级应用部署中,前端静态资源的性能优化至关重要。JeeSite Vue3前
前端企业应用CMSflutter_tts完全指南:跨平台文本转语音插件的终极入门教程
flutter_tts完全指南:跨平台文本转语音插件的终极入门教程 flutter_tts是一个功能强大的Flutter文本转语音插件,能够帮助开发者快速实现跨
语音音频移动开发Nginx 配置实战速查手册:从虚拟主机、反向代理到缓存与安全加固
Nginx 配置实战速查手册:从虚拟主机、反向代理到缓存与安全加固 本指南以开源仓库 reference https://link.gitcode.com/i/
文档教程知识库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考