生产环境部署 Vanity:Unicorn/Passenger 下数据库连接管理避坑指南
【免费下载链接】vanityExperiment Driven Development for Ruby项目地址: https://gitcode.com/gh_mirrors/va/vanity
Vanity 是一个面向 Rails 的 A/B 测试框架(Experiment Driven Development),支持 Redis、MongoDB、ActiveRecord 等多种数据源,通过experiments/目录下的 Ruby 文件定义实验,开箱即用。然而把 Vanity 部署到生产环境时,一个极其隐蔽的坑会悄悄出现:在 Unicorn、Passenger 这类 fork 型 Web 服务器下,Vanity 的数据库连接如果不做管理,就会出现连接失效、数据写错、实验数据丢失等问题。本文围绕生产环境部署 Vanity 的连接管理,给出完整避坑方案。
一、先搞懂问题:为什么 fork 之后 Vanity 连接会失效?
Unicorn 与 Passenger(尤其是 smart spawning 模式)都是"先启动 master 进程,再 fork 出多个 worker"的模型。fork 出来的子进程会继承父进程的文件描述符,包括已经建立的 Redis TCP 连接、ActiveRecord 连接池等。
问题出在这里:
| 风险点 | 后果 |
|---|---|
| master 的连接被多个 worker 共享 | 同一 socket 被并发读写,数据互相污染 |
| 部分连接在 fork 后处于异常状态 | 直接使用报错,甚至进程崩溃 |
| 连接未隔离 | 实验参与者分组、转化数据错乱 |
所以生产环境部署 Vanity 的第一原则是:每个 worker 进程都要建立自己独立的数据库连接。
二、Vanity 连接管理核心 API:connect! / disconnect! / reconnect!
Vanity 提供三个核心方法(定义在lib/vanity/vanity.rb):
Vanity.connect!:建立连接,参数可来自config/vanity.yml,也可显式传入Vanity.disconnect!:关闭当前连接Vanity.reconnect!:先断开再重连,等价于 disconnect! + connect!
连接对象由lib/vanity/connection.rb的Vanity::Connection管理,会根据配置自动选择 Redis、MongoDB 或 ActiveRecord 适配器(各适配器实现见lib/vanity/adapters/)。
三、Unicorn 部署 Vanity 的正确配置:after_fork 里重连
在config/unicorn.rb中加入:
after_fork do |server, worker| defined?(Vanity) && Vanity.reconnect! end两个关键点:
- 每个 worker fork 完成后都会重新建立连接,互不干扰
- 用
defined?(Vanity)做防御性判断,避免在 Vanity 尚未加载的进程中报错
四、Passenger 部署 Vanity:自动重连机制与手动兜底
好消息是,Vanity 在lib/vanity/frameworks/rails.rb中已经内置了 Passenger 的重连逻辑:通过PhusionPassenger.on_event(:starting_worker_process)监听 worker 启动事件,fork 后自动调用Vanity.playground.reconnect!。
不过为了稳妥,官方仍建议在 initializer 中手动兜底:
if defined?(PhusionPassenger) PhusionPassenger.on_event(:starting_worker_process) do |forked| # 仅在 smart spawning 模式(fork 场景)下重连 if forked defined?(Vanity) && Vanity.reconnect! end end end注意forked参数:Passenger 在非 fork 场景下不需要重连,只处理forked == true的情况即可。
五、显式连接参数时的顺序坑:先 disconnect 再 connect
如果你使用显式参数建立连接(例如复用已有的 Redis 连接):
Vanity.connect!( adapter: :redis, redis: $redis )那么在 fork 后的重连流程中,必须先断开旧连接再建立新连接:
Vanity.disconnect! Vanity.connect!( adapter: :redis, redis: $redis )否则来自 master 的旧 socket 会残留,导致连接泄漏和状态错乱。Vanity.reconnect!内部正是按这个顺序执行的,这也是它被推荐用于 fork 场景的原因。
六、生产环境数据源配置:config/vanity.yml
连接参数默认从config/vanity.yml读取(读取逻辑在lib/vanity/configuration.rb),按环境区分。Redis 生产配置示例:
production: adapter: redis url: redis://<%= ENV["REDIS_USER"] %>:<%= ENV["REDIS_PASSWORD"] %>@<%= ENV["REDIS_HOST"] %>:<%= ENV["REDIS_PORT"] %>/0使用 ActiveRecord 作为数据源时:
production: adapter: active_record active_record_adapter: postgresql <% uri = URI.parse(ENV['DATABASE_URL']) %> host: <%= uri.host %> username: <%= uri.user %> password: <%= uri.password %> port: <%= uri.port %> database: <%= uri.path.sub('/', '') %>建议把敏感信息全部用环境变量注入,避免密钥写进版本库。
七、更多生产环境避坑要点
- rake 任务自动跳过连接:
lib/vanity/autoconnect.rb内置了一份任务黑名单,db:migrate、assets:precompile等任务不会触发连接建立,避免部署流程中无谓报错 - 数据源故障优雅降级:开启
config.failover_on_datastore_error = true,当 Redis 等数据源异常时,Vanity 会调用on_datastore_error回调记录日志而不是直接抛异常,保证主业务不受影响 - VANITY_DISABLED 环境变量:临时停用 Vanity(如维护窗口)时设置该变量即可,连接不会建立
- Redis 连接建议复用:
lib/vanity/adapters/redis_adapter.rb中会自动设置thread_safe: true,并支持传入已有的 Redis 实例(redis: $redis),与你的连接池统一管理
八、如何验证生产部署是否正常
部署完成后,建议按以下顺序检查:
- ✅ 打开
/vanity报告页面,确认实验与指标数据正常显示 - ✅ 观察 worker 日志,确认没有连接相关报错
- ✅ 用
vanity report --output vanity.html生成本地报告,抽查参与人数与转化数是否符合预期 - ✅ 重启 worker 后再次访问,确认重连机制生效(这是 fork 场景最容易出问题的地方)
搞定以上几点,Vanity 在 Unicorn / Passenger 下的生产环境部署就基本不会踩连接管理的坑了。
【免费下载链接】vanityExperiment Driven Development for Ruby项目地址: https://gitcode.com/gh_mirrors/va/vanity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考