部署 Ruby 的多线程应用服务器介绍

robbin · 2013年05月08日 · 最后由 wangping 回复于 2015年04月24日 · 21152 次阅读
本帖已被管理员设置为精华贴

随着 Rails4.0 的发布,Ruby 的 Web 开发社区开始进入多线程的时代了:

  1. 从 Ruby1.9 开始,多线程已经是 native thread 了,尽管有 GIL 全局锁的存在,但是对于 IO 并发来说,已经可以实现并行处理了。
  2. Rails4.0 开始,默认打开了多线程运行模式,将推动整个 Ruby 的 Web 开发社区迁移到多线程 Web 模式。

多线程服务器的模型,相比传统多进程服务器模型,可以非常有效的提高 IO 并发的吞吐量。我现在开发 Web 应用,已经全部改用多线程了,我写过一个相关的文档介绍:Web 并发模型粗浅探讨,有哪些 Ruby 的应用服务器可以良好的支持多线程呢?

Rainbows

rainbowsunicorn是同一个作者 Eric Wong 开发的,rainbows 和 unicorn 非常像,他们之间的主要区别就是:unicorn 是多进程服务器,rainbows 是多线程服务器,此外基本用法,配置都一样的,本身 rainbows 就是基于 unicorn 开发的。

unicorn 现在被广泛的使用在很多负载非常高的生产环境中,rainbows 也和 unicorn 同样非常稳定。由于 Ruby1.9 的 GIL,多线程并发只能有效使用 1 颗 CPU 内核,因此在多核服务器上,需要运行多个 rainbows 进程。一般来说,服务器有多少 CPU 内核,就启动多少个 rainbows worker 进程。每个 rainbows 进程里面再启动多个线程,因此理想情况下,能够同时处理的 IO 并发请求数量等于“进程数” × “线程数”,看一个例子:

rainbows.rb,注意以下 3 项配置:

use :ThreadPool # 使用线程池模式,进程启动的时候创建好线程数 worker_processes 4 # 创建多少个进程 worker_connections 64 # 每个进程创建多少个线程

以上面的配置为例,如果你在一台 4 核服务器上面部署应用,希望尽量使用服务器资源,那么可以创建 4 个进程,每个进程创建 64 个线程,因此整个服务器 IO 并发处理能力是 256 个执行线程,这比传统的多进程模式要高得多。

控制 rainbows 服务器的 shell 脚本很容易写:rainbows.sh,rainbows 和 unicorn 一样,都是通过给进程发送信号来控制服务器的。

以上的配置启动以后,会有 5 个 rainbows 进程:1 个 master 控制进程,4 个 worker 工作进程。master 进程负责加载应用程序代码,创建和销毁 worker 进程,分发请求;worker 进程负责处理请求。这个 master-workers 的工作模式和 nginx 是一样的。你还可以通过给 master 进程发送信号,让 master 进程创建更多 worker 进程,或者减少 worker 进程,还可以实现“平滑的重启”,即在不中断 web 请求服务的同时,重新启动进程,加载新的应用代码,这一点通过: rainbowsctl reload就可以实现。

此外如果你使用的是 Ruby2.0,还可以在rainbows.rb里面打开 copy on write 特性,如下:

preload_app true GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true

可以让多个进程共享加载应用程序框架和库的内存空间,节省很多物理内存。

总之对于大型多线程 Ruby Web 应用,推荐使用 rainbows,我使用下来感觉也很不错。

Puma

我在相当长一段时间都不怎么关注 puma,因为 puma 是个单进程多线程服务器,没有 cluster 模式,不太适合真实有负载的环境,对于多核服务器,启动多进程跑 cluster 是必须的。但是最近 Puma 升级到 2.0 版本以后,也支持了多进程 cluster 模式,而且也是 master-workers 的方式,类似 nginx 和 rainbows,请看配置文件:puma.rb

workers 0 # 指定创建多少个 worker 进程,如果 0 则不打开 cluster 模式 threads 0, 16 # 最少线程和最多线程,可根据请求量在这个范围自动伸缩

通过 workers 设置进程数,通过 threads 设置线程数,最大 IO 并发请求量等于进程数乘以最大线程数。Puma 比较有意思的地方在于:

  1. 通过 workers 设置,可以在单进程和多进程 cluster 模式之间自动切换,如果是 0,则不启动 master 进程,只有一个单进程,如果 workers 不是 0,则启动 1 个 master 和多个 workers。而且 master 进程不会加载应用代码,master 进程占有内存非常少,当然这个的代价就是如果启动 workers 进程很多,启动时间会很长。
  2. 线程可以根据请求量自动在 min 和 max 之间伸缩,对于较少的请求,启动的线程数少,可以节省资源。

此外 puma 也支持“平滑的重启”,也提供了很多有趣的状态控制方式,请看puma.sh Puma 启动的时候可以设置一个状态文件,它会把进程的运行状态写进去,方便你控制进程的运行。

Rainbows or Puma

我对 Rainbows 和 Puma 做了一些简单的性能压力测试,测试结果表明两者之间的性能差异非常小,Rainbows 稍稍胜出。使用哪个,更多取决于个人偏好。我觉得大型应用使用 Rainbows 更好一些,希望节省服务器资源的小型应用更适合用 Puma。这是因为:

  1. Rainbows 基于 unicorn 的代码,稳定性得到了大量高负载应用的考验。
  2. Rainbows 提供了很多通过信号控制进程的手段,方便系统管理员干预应用服务器运行。
  3. 在单进程模式下 Puma 只有 1 个工作进程,单 Rainbows 仍然会启动 master-worker 模式,因此 Puma 的单进程更节省内存。
  4. Puma 的线程可以根据请求自动伸缩,对于请求量小的应用,比较节省资源。

Zbatery

zbatery也是 unicorn 和 rainbows 的作者 Eric Wong 开发的,配置文件可以直接用 rainbows 的,它是 rainbows 的简化版本,只支持单进程多线程,没有 master 进程,信号支持的也不完整,而且很久没有升级过了。比较正规的生产环境可能就不太适合了。但 zbatery 的好处是特别节省内存,比单进程的 puma 还要节省很多内存,如果你想在一个 VPS 上跑很多 web 应用,每个 web 应用流量都很小,那用 zbatery 到非常合适。

前段时间刚好用了 rainbows,关注中!谢谢 robbin 的讲解。

@robbin 那和 Passenger 比如何?我就觉得用 Passenger 要编译比较麻烦……性能方面,没有做过对比。。。

#2 楼 @imlcl Passenger 性能不如 unicorn/rainbows,这个我测试过,而且差的还挺多。Passenger 最大的毛病是和 nginx 绑定的太紧密,每次升级 passenger,要连带升级 nginx,迁移很多 nginx 的配置,非常痛苦。

#3 楼 @robbin 是,你有没有试 4.0.0 的?已经 release 了,听说性能和稳定性都改进了。我用这东西是因为刚学 Rails 时,觉得这东西配起来比较方便一点。但后续就和你所说,麻烦,每次都得编译成模块加载。看来我也试试布署 rainbow 或 puma 吧

这些服务器都使用了 Zed Shaw 的 mongrel 遗留下来的用 ragel 生成的基于状态机的 html header parser...

#5 楼 @luikore 这意味这什么呢?

@robbin不知道和 thin 比起来怎么样,一直在用 thin。

说明 Zed Shaw 的代码很经典,孵育了多个很棒的应用服务器,他离开 Ruby 届真是一大损失。

学习了。。

#7 楼 @zlfera 比 thin 肯定要好,thin 只是基于 EventMachine 做了个事件驱动,EM 的并发性能你懂的,Puma 是基于多线程的 ReActor 的机制 , 类似 Java 的 NIO 机制

#7 楼 @zlfera Thin 基于 EventMechine,有特别的用处。 Thin 是单线程多进程的方式,并且没有 master 进程

#11 楼 @huacnlee @zlfera 我 development 时就用 thin,好像 ruby-china 也是吧

@robbin 如果 rails 跑多线程,大家都知道的,多线程共享数据区,所以对我们写的 ruby 代码有没有什么地方需要特别注意的,特别是 rails 里面。

@robbin rainbows 的 shell 脚本 rainbowsctl 无法访问(404)。能否贴哈脚本代码。

#15 楼 @outman 改了个文件名,现在可以了。

#13 楼 @outman 没什么特别需要注意的,database.yml 里面 connection pool 的数字要和你线程池的数字保持一致,另外避免在内存当中维持全局可写的变量。

#6 楼 @jasl 这意味着你该紧跟 Zed Shaw 的脚步...

#18 楼 @bhuztez 好吧。。。。

#6 楼 @jasl 意味着 Zed Shaw 有多么牛逼....

37signal 在不少地发使用了 rainbows... 而且效果不错,至少稳定性是得到了验证的...

Puma 发展很迅速(这点很可观),我也用在一些比较小的项目中了。

最近刚把自己的项目跑在 Puma 上了,好文章收藏了

额。学习了,现阶段还是 development 模式。。连 redmine 都是。。。

大家有必要了解一下 node.js 的,1,天生 non-blocking io, 性能没法说,cluster 是标准库提供的;2,社区逐渐丰满,几乎 ruby 的好东西都被复制到 node 上了。3,前后端通吃

一直用 thin,不喜欢 unicron 的配置方法,感觉 thin 配置起来更符合我习惯,而且在考虑用 eventmachine。

如果不用 Rubinius 或 JRuby,不建议使用 puma,感觉 unicron 是最稳妥的方案

#25 楼 @kevinxu 哈哈,不支持也不可能支持多线程模式恰恰是 node.js 的致命伤之一

non-blocking server / app 在 ruby 也有很多,性能和 node 区别不大。而且 ruby 有 Fiber 支持不需要像 node.js 那样写 callback 套 callback (不过现在 node.js 的写法有改进了). 不过就和这个话题没什么关系了。

提到 Zed Shaw 是想起

多进程模式部署的话 (现在性能和内存表现均最佳的是 eventmachine + 多进程), 服务器间多进程共享数据的最佳方案是使用 Zed Shaw 推荐的 0mq 进行通信...

mongrel2 就是去掉了多线程模式

一直用 thin 比较多,现在看来要尝试一下 rainbows. Rails 一直很少强调多线程,线程安全,Rails4 不知道会不会遇到什么问题。

谢谢 robin 的分享 之前用 passenger 后来服务器升级的时候换了 unicorn 前几周新装 redmine 试了一下 puma,结果 puma 莫名地中止了几次 简单查了一下,好像原因是 rails 调用了系统命令,但某些命令不存在,rails 抛了 exception,puma 进程可能跟着退出了。 现在换了 unicorn 没发生这种现象。

等闲的时候再仔细研究一下 puma 退出原因。

#28 楼 @luikore callback 套 callback 到不是问题;我一般做法是 1,用 coffee,2,好的模块划分,3 async 等类似的库

nodejs 算是单进程多线程(libuv 用多线程+non-block io 做异步),我说的 cluster 是在进程级别更好的利用多核的 cpu 资源

至于孰好孰坏,难分伯仲,各有各的应用场景吧 - 我个人还是 ruby fans :)

楼主发贴,必属精品

先不急看正文,正在看楼主开头给出的 PPT。PPT 看到一半已经收益良多了,忍不住来回帖表达下感谢。

跑一下题,@robbin 我在 csdn 会员还有半年的剩余期限的情况下又充了一年的值,可是新充的覆盖了以前的,换句话说我之前那半年的就没了,这个问题谁能搞定?

ruby 性能变更强啦。

#36 楼 @xlgwr 强的不明显

#27 楼 @SharpX unicron 比 thin 有什么优点呢?

希望 ruby app server 越来越强啊,要不 ruby 大面积推广有困难啊

#30 楼 @jimrokliu 怎么感觉头像像是昨天的我们来约会吧的男嘉宾

最近在用 puma,感觉挺爽的,不过好像没有 unicorn 的 zero downtime,还是我没发现?

@robbin 问个 puma 的内存消耗问题 机器是 4 核,8G,ubuntu 12.04,puma 起的是 2 个 worker,线程是 0:16 刚开始,每个 worker 只有 100Mb 左右,这才一个星期,内存增长到每个 500Mb。这种情况正常吗?

#42 楼 @iamroody 你换成 rainbows 比对一下,如果 rainbows 每个 worker 也增长到 500mb,那就是你程序内存泄露了,如果 rainbows 内存不高,说明 puma 有问题。

我自己对比观察下来,确实发现 puma 长期运行,内存占用似乎比 rainbows 高一些。

#43 楼 @robbin 多谢,准备测试一下!除了这种方法以外,还有其他更方便的方法来检测 rails 程序的内存泄露吗?

#43 楼 @robbin 看到你之前的一篇文章《监视 Rails 进程内存泄漏的技巧》,正在学习中 http://www.iteye.com/topic/307271

是不是就不用必须启动多个服务了呢

切换到 puma,遇到好多不稳定的问题,现在暂时又切回 unicorn 了,sigh~

@robbin jruby 下设置了 workers 4,如何能对比出 workers 0 的区别?

rainbowsctl reload 具体如何实现的呢?

lgn21st Unicorn fork 的多个进程之间数据是否可以共享 提及了此话题。 04月25日 09:19
需要 登录 后方可回复, 如果你还没有账号请 注册新账号