Rails Ruby-China.org 选择用 Thin 还是 Unicorn?

lgn21st · 2011年11月06日 · 最后由 realwol 回复于 2016年12月09日 · 21282 次阅读

问题的起因是这样的:Ruby-China.org 部署在一个 Linode 512 的 Instance 上,系统只有 512M 内存,跑一个 Nginx 带两个 Thin 的 instance,外加 MongoDB,Redis,还有一些后台任务等等。前几天 @huacnlee 跟我说内存不够用了,两个 Thin 和 MongoDB 一上去内存就捉襟见肘了,需要考虑升级 VPS 的内存了,否则极有可能随时把其他的 Services 如 MongoDB 或者 Redis 搞挂掉。

我当时建议用 Unicorn 试试看,Github 和 37Singles 都全部迁移到了默认使用 Unicorn 作为 Rails 的 server。昨天找了个时间测试了一下,开启 2 个 Unicorn 的 worker_processes,发现每个 worker 的内存占用跟 Thin 基本上一致,也就是说使用 Unicorn 并没有内存使用上的优势。

我的理解是不管是 thin,mongrel,webrick,还是 unicorn,都需要载入一份完整的 Rails instance,Rails instance 的内存消耗是无法通过替换 Server 消减的。另外还有一个 Memory Bloat 的问题,当 Rails server 运行时间越长,占用的内存就越多,罪魁祸首是大量的 ActiveRecord 对象被 hold 在内存中,无法高效及时的释放掉,所以通常的作法是设定一个阈值,当 Rails Server 使用内存超过一个具体的数值一段时间后,就 Restart 这个 Rails Server,这个方法在 Production 环境下已经被充分验证过是一个行之有效的解决方 案。

Unicorn 的这种 Master process + worker processes 的工作方式,在总体内存消耗上比 Thin 要略微大一点点,不过 Unicorn 相对于 Thin 的优势在于他的 Load balance 机制是通过 OS Kernel 来实现的,以及进程管理非常的 Unix 风格,有利于简化部署和系统管理: http://sirupsen.com/setting-up-unicorn-with-nginx/

Load balancing between worker processes is done by the OS kernel. All workers share a common set of listener sockets and does non-blocking accept() on them. The kernel will decide which worker process to give a socket to and workers will sleep if there is nothing to accept().

Thin 需要通过 Nginx 来做 Load balancing,或者在多个 Thin instance 前面架设一个 HAproxy 来实现高效的 Load-balancing,不过就系统组成复杂度,部署难易程度,以及 Load balancing 效能方面考虑,优选采用 Unicorn 的方案。

话说回来,我们仍然需尝试在其他地方压榨一些内存出来,或者考虑换回使用 32bit 的 Linux?

这里有一篇关于 Twitter 使用 Unicorn 的文章,他们使用 Unicorn 替换掉了 Mongrel 后,处理 request 的时候降低了 30% 的 CPU 消耗,并使用一个定制的脚本自动监控并重启 Unicorn workers。 http://engineering.twitter.com/2010/03/unicorn-power.html

在 Github 上有一篇 Blog,讲的是 Github 是如何使用 God 来监控 Unicorn 的,包括根据 CPU 的占用或者内存消耗自动启动/重启 Unicorn。 https://github.com/blog/519-unicorn-god

现在一个 server 占了多少内存呢?我一个类似网站,功能可能少点,passenger 一个 ruby 进程 40M。如果太高的话可能要检查下代码。

unicorn 的一个 worker processes 占用内存 40~44M,开两个 worker processes 连带 master 进程 unicorn 占用内存不过 120~130M 左右吧?还剩下 380M 内存难道不够?会否问题出现在 MongoDB 上?之前试用过一下 MongoDB,似乎它会将剩下的内存全部占光。

#3 楼 @Rei #4 楼 @Los 有截图为证,不过现在显示图片有点问题,呼唤 @huacnlee 帮助修复。 Thin 或者 Unicorn 的一个 proces 启动后,立即占用 90MB 左右的内存,随着时间推移,可能会占用到 140MB 左右,甚至更多,我们可以开一个新帖分析一下内存消耗情况。这里我贴个 skitch 的截图: https://skitch.com/lgn21st/ggmmk/root-li387-252-ssh 从截图可以看到,两个 Thin 和 resque 是内存使用大户。resque 貌似也载入了一个完整的 rails 环境,内存消耗接近 90MB。

#5 楼 @lgn21st 图片绑定那个 l.ruby-china.org 的域名,DNS 貌似不对啊

#4 楼 @Los 和 MongoDB 没有关系,Rails 跑起来,一个 Thin 进程就有 90M 左右,目前 Mongodb 的进程才 15M

@lgn21st 的描述上看不管换 Thin 还是 unicorn 都无法解决问题。用切腹的方式也只是暂时缓解问题。 我觉得主要原因是用了太多的 gems(https://github.com/huacnlee/ruby-china/blob/master/Gemfile.lock ),有些功能重复的,比如 Mongoid 和 ActiveRecord。 至于 Memory Bloat 问题是完全可以人为控制的。 还有以目前的规模和需求来看,像 resque 这样的吃内存大户也不需要用,发邮件时 fork 一下也 ok。

我的 linode 512 上,32bit Arch Linux,nginx,unicorn 两个 worker,postgres,redis,resque 两个 worker,resque-scheduler 内存在 400 左右,内存压力不大

之前在 aws 上用的 passenger,每个 worker 的内存消耗和 unicorn 差不多。 resque 的 worker 也占这么多内存,所以应该是 rails 环境占的。

之前在 aws 上的时候经常内存爆。但是我看 linode 有 swap 吗,内存不够的时候是不是会用到 swap?是不是虽然性能慢点但不会因为无法分配内存而出错?

#8 楼 @hooopo activerecord 有依赖,但是没载入。

就我个人而言,Unicorn 相比于 Thin 并没有太大的优势。当项目大了之后,更需要细粒度的部署,我会倾向于 Nginx + HA + Thin。这个内存的问题目前的解决方案最好是 resque 先去掉

暂时还是将 Resque 关了,反正目前邮件提醒仍然有 Bug

#12 楼 @huacnlee 邮件提醒功能很重要吗? 可以统计一下有多少人需要回复的邮件提醒

#11 楼 @nowazhu Unicorn 的热部署优势很明显啊

邮件提醒不需要吧, 那样人们就会等着邮件提醒,不会上来再看了,对论坛发展不好

我有国内的独立服务器可以贡献出来 需要的话可以联系我 dave@liageren.com

#17 楼 @huacnlee 服务器? 算是靠谱吧,我自己的, 现在在跑我的网站,如果能为社区做点事也不错

#18 楼 @dave 我找个时间跟你通个电话,了解一下,你有我的号码么?在 Twitter 上 DM 我一下吧

#19 楼 @lgn21st skype 上发给你了

#17 楼 @huacnlee 我刚刚跟 @dave 通了个电话,@dave 是我的前同事,我了解了下情况,考虑到他的服务器目前用于他们自己的商业项目,虽然有大量的系统资源空闲,但是却无法完全开放权限给我们。所以我建议他们的服务器资源作为我们的 backup。我们的社区发展下去,如果需要的话我就去升级 Linode 到 1024 或者购买位于日本的 EC2,这个论坛的方向靠谱,不管是小额捐赠,或者大公司支持,我都有把握解决硬件资源问题。

apache + mongrel apache + fastcgi nginx+ passenger nginx + Haproxy + thin apache + passenger apache + unicorn

以上组合我都用过, 最后我还是选择了 nginx + passenger , 只因为 重启 忒方便

touch tmp/restart.txt

呵呵~!!!

#22 楼 @wxianfeng unicorn 也不麻烦,kill -USR2 pid。好处是用户不会被中断。之前用 passenger 重启之后第一次访问要花点时间

为了用 Http Streaming,现在改成了 unicorn 不过无缝重启还没有搞定

无缝重启已经解决了,原来是这样的:

用这个命令就能通知 unicorn 进程重启

kill -USR2 `cat /rails/app/path/tmp/pids/unicorn.pid`

但是之前一直没成的原因是 unicorn.rb 里面有项 preload_app 开启了,将它去掉就可以用上面的命令重启

preload_app true

http://stackoverflow.com/questions/5794176/restart-unicorn-with-a-usr2-quitting-old-master

#6 楼 @huacnlee 我这儿看不到图片,404

#26 楼 @blankyao 暂时有问题,正在想办法解决

#23 楼 @cqpx Nginx + Passenger 重启我一般使用 Nginx 的 reload 功能,完全是无缝重启,也不怕服务器会中断服务 nginx -s reload

另外,上面大家都提到什么占用多少内存之类的,用什么监控的啊?直接用 top 么?

top 就可以看到了

#28 楼 @yzhrain 最好还是

touch tmp/restart.txt

不是说 passenger + ree 可以减少 30% 内存占用么.. 我看他原理就是共享了一部分 framework 占用的内存空间 ,貌似是先加载框架,然后再 fork , 我测试下来也是, 跑的实例越多 , ree 在内存占用上的优势越明显

个人觉得 unicorn 最大的优势还是在于处理长动态请求, 因为他是基于事件驱动模型, 而且看了很多测试, 单个请求的速度 unicorn 可能还比 thin 或者 passenger 略微要慢一点

#30 楼 @Rei 我觉得吧,用

touch tmp/restart.txt

其实也能实现优雅的重启 Unicorn 进程。不过我在想的是服务器上跑的不仅仅只有 Rails server, 还有数据库,redis,以及 resque 等等后台进程,跑一个 monitor 来监控这些所有的后台 Services,并自动完成 deploy 后重启。

Passenger 和 Thin 以及 Unicorn 背后的 Philosophy 和 Architecture 不同,就单机 VPS,小网站而言,Passenger 没有什么不好,不过我更认可 Unicorn 用 Unix Domain Socket 来完成进程间通讯,Loading balance 基于 OS 内核调度来实现,fork 一个 worker processor 出奇的快,配合 god 或者 monit 监控并发送 singal 来管理/切换服务器进程非常平顺,然后一切都尽在 Capistrano 掌控之下。

@lgn21st 管理一组应用进程用 foreman 比较不错:http://blog.daviddollar.org/2011/05/06/introducing-foreman.html

Heroku 用的就是 foreman,deploy 以后所有进程一起重启。 在开发环境用起来也很方便。

http://ruby-taiwan.org 跑在 nginx + passenger 上....

32 位下 MongoDB 有 2G 的限制。

数据量大了后,MongoDB 内存要求会上去,它的设计哲学是尽量把 index 等放内存。不足时自动用系统调度 swap(nmap)。这个时候就等着 otm 然后被系统干掉。在 512 的 VPS 上,插十万条数据然后做几个 mapreduce 就能搞出这问题来。另外楼上有朋友说的没错,它是有多少内存用多少的。

resque 可以用 hirefire 这样的库来即使 fork,省的大部分时间空占内存。

关键在于可能的话还是多加些内存:)

现在资源已不再是问题,有盛大支持

打开 copy-on-write friendly gc 以后, 很多 gem 占用的内存其实只有一份

#1 楼 @lgn21st 发现 linode 的部署这个,巨慢无比~~ 本地还挺快的~~

@huacnlee 刚看到 ruby-china server 又从 unicorn 改到 thin 了,能说下原因吗?

#39 楼 @camel 为了用 EventMachine

nginx+Passenger 和 ExtJS 实在是合不来,本来意图就是用哪里加载哪里,结果 nginx 给全绑一块儿了,研究了半天也不会取消,速度慢且不说,还有一堆重复加载的问题,实在是怕了它了 还是用了 Thin,世界清静了……效率什么的再说吧=_=

#3 楼 @Rei 想请问下,内存不断上涨的话,检查代码,要检查哪些地方,比如你在写代码的时候,有哪些考虑

进程内存达到一定阈值后,重启进程,如果这个时候进程正在处理请求,会出现问题吗

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