之前我介绍了 puma 的三种启动方式, normal, hot, 以及 phased 启动方式
虽然从效果上来说, phased restart 启动方式最好, 配置也最简单, 只需要替换 kill 信号量到 SIGUSR1 就可以了,
但是 phased restart 也有非常明显的问题:
那么 退而求其次,我们能否在保证 0 downtime 的情况下解决这个问题呢?
答案是 利用 socket.activation 实现 hot restart。建立了 socket.activation 之后, 在 puma.service 在重启的时候, 消息会先进入这个 socket.activation 进行处理, 此时访问并不会返回失败, 而是 holding 中, 当 puma.service 重启完毕之后 这个 socket 就会将存储的连接丢给 puma.service 来处理。
我这里举个例子 比如 我们原来有一个 叫做 puma.service 的 systemd 服务, 为了建立对应的 socket 我们需要在 /etc/systemd/system/ 中建立 puma.socket , 注意 这里名字必须和 service 对应
[Unit]
Description=Puma HTTP Server Accept Sockets
[Socket]
ListenStream=0.0.0.0:9292
# AF_UNIX domain socket
# SocketUser, SocketGroup, etc. may be needed for Unix domain sockets
# ListenStream=/run/puma.sock
# Socket options matching Puma defaults
NoDelay=true
ReusePort=true
Backlog=1024
[Install]
WantedBy=sockets.target
特别注意的是, 这里的 ListenStream 必须和 puma.rb 中的 bind 对应起来(不对应 socket 就不知道往哪转发了)
之后我们要修改 /etc/systemd/system/puma.service 将依赖添加进去
[Unit]
Description=Puma HTTP Server
After=network.target
Requires=puma.socket # 看这里 添加这个依赖
[Service]
# Puma supports systemd's `Type=notify` and watchdog service
# monitoring, if the [sd_notify](https://github.com/agis/ruby-sdnotify) gem is installed,
# as of Puma 5.1 or later.
# On earlier versions of Puma or JRuby, change this to `Type=simple` and remove
# the `WatchdogSec` line.
Type=notify
# If your Puma process locks up, systemd's watchdog will restart it within seconds.
WatchdogSec=10
User=deploy
WorkingDirectory=/var/www/pushconfig/current
ExecStart=/home/deploy/.rvm/bin/rvm 2.7.3 do bundle exec puma --keep-file-descriptors -C /var/www/pushconfig/shared/puma.rb
# 注意这里的--kep-file-descriptor 如果是使用的 bundle exec, 一定要加这个参数
# ExecReload=/bin/kill -TSTP $MAINPID
ExecReload=/bin/kill -SIGUSR2 $MAINPID #注意这里,可以换为 USR2, 用来启动
StandardOutput=append:/var/www/pushconfig/shared/log/puma_access.log
StandardError=append:/var/www/pushconfig/shared/log/puma_error.log
# ......
之后 需要运行以下指令
当然在进行这些操作之前, 建议你先停止老的版本的 puma.service
当启动完毕之后 puma 的 hot restart 功能就设置完毕了 此时, 无论你如何重启, 用户都不会直接 503
如何验证呢? 你在这个时候可以 stop puma.service 甚至 kill 掉 puma.service 的主进程!
按照常规, 这个时候你访问网站 网站的 nginx 肯定是报 503,但是现在,感觉是访问卡了一下, 之后正常 200 了!
为何出现这个情况呢? 是因为 puma.socket 首先会先将你的访问 hold 住, 同时去 找自己同名的 service 去处理, 如果 service 没启动, 就会启动这个 service
如果你是用 stop 命令 来关闭 service 这个时候你也会看到 一个 warning
[email protected]:/etc/systemd/system$ sudo systemctl stop PumaSSLGuala.service
Warning: Stopping PumaSSLGuala.service, but it can still be activated by:
PumaSSLGuala.socket
当启动完毕 service 后 socket 就会将之前暂存的连接丢给 service 继续处理。 于是之前的访问就能收到 新启动的 puma 返回的 200
你看 连 kill 都没问题,restart 自然不在话下
可以自行测试, 重启 puma 的时候, 另外个 terminal 拼命访问, 感觉就像是网突然卡了一下, 接着正常了。
这是我配置时候总结出来的一些注意点
--keep-file-descriptors
参数, 否则 puma 就启动失败, 报 `for_fd': Bad file descriptor - not a socket file descriptor 这个错最后说下 capistrano-puma, 其实我并不是手写 systemd 的 service, 而是使用的 capistrano-puma 的 systemd 帮我生成 service 的文件。
如果你跟我一样, 也是用 capistrano-puma 生成的 puma 的 systemd 服务, 那么遗憾的告诉你 capistrano-puma 目前(2021 年 08 月 26 日, 5.0.4 版本 )还不支持 好消息是 已经有网友发了个 PR https://github.com/seuros/capistrano-puma/pull/324 可以生成这种 hot restart 的方式的 坐等官方 merge