Ruby puma 只会配 phased-restart ? 是时候配一个 hot restart 了

jicheng1014 · 2021年08月26日 · 最后由 zj0713001 回复于 2021年08月27日 · 537 次阅读

之前我介绍了 puma 的三种启动方式, normal,hot, 以及 phased 启动方式

虽然从效果上来说,phased restart 启动方式最好,配置也最简单,只需要替换 kill 信号量到 SIGUSR1 就可以了,

但是 phased restart 也有非常明显的问题:

  1. 当 gemfile 有更新的时候,phased 更新会出现找不到 gem 的 问题
  2. 无法修改 config/puma.rb 文件,以及部分 yml 修改无效
  3. 新老程序的 worker 会同时存在,有的业务场景一旦新逻辑上线,再走老逻辑就会出问题

那么 退而求其次,我们能否在保证 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

# ......

之后 需要运行以下指令

  • systemctl daemon-reload # 用于重新加载 systemd,毕竟文件变动了
  • systemctl enable puma.socket puma.service # 启用 socket 的重启后自动运行
  • systemctl start puma.socket puma.service # 启动

当然在进行这些操作之前,建议你先停止老的版本的 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 拼命访问,感觉就像是网突然卡了一下,接着正常了。

这是我配置时候总结出来的一些注意点

  1. jruby 不支持 socket activation,所以如果你是用 jruby 跑的,对不起 用不了
  2. socket 里 bind,必须和 puma 中的 puma bind 的地址是一致的。比如你 puma.rb 监听 9292 端口,那你 socket 就必须是 9292
  3. socket 和 service 必须同名。比如 你的 puma 的 service 叫 app.service 那你的 socket 就得是 app.socket
  4. 最后,也是坑了我一阵的,是如果你跟我一样 在操作 puma 的 service 里,是以 bundle exec 来启动 puma 的,请务必在后面加入 --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

1 楼 已删除

https://github.com/windy/mina-ng-puma 要是用 puma 的话可以试试我写的一个 gem 包。

lyfi2003 回复

puma 5 之后的版本移除了 daemon 模式转而推荐 systemd 的方式跑啦

也可以使用 K8s,配置成蓝绿发布。

a112121788 回复

别提了 我 mac 的 k8s 到现在都还没跑起来

jicheng1014 回复

确实 k8s 更好一些。。。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号