问题的起因是这样的: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
unicorn 的一个 worker processes 占用内存 40~44M,开两个 worker processes 连带 master 进程 unicorn 占用内存不过 120~130M 左右吧?还剩下 380M 内存难道不够?会否问题出现在 MongoDB 上?之前试用过一下 MongoDB,似乎它会将剩下的内存全部占光。
从@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?是不是虽然性能慢点但不会因为无法分配内存而出错?
就我个人而言,Unicorn 相比于 Thin 并没有太大的优势。当项目大了之后,更需要细粒度的部署,我会倾向于 Nginx + HA + Thin。这个内存的问题目前的解决方案最好是 resque 先去掉
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 重启之后第一次访问要花点时间
无缝重启已经解决了,原来是这样的:
用这个命令就能通知 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
不是说 passenger + ree 可以减少 30% 内存占用么.. 我看他原理就是共享了一部分 framework 占用的内存空间 ,貌似是先加载框架,然后再 fork , 我测试下来也是,跑的实例越多 , ree 在内存占用上的优势越明显
个人觉得 unicorn 最大的优势还是在于处理长动态请求,因为他是基于事件驱动模型,而且看了很多测试,单个请求的速度 unicorn 可能还比 thin 或者 passenger 略微要慢一点
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 以后所有进程一起重启。 在开发环境用起来也很方便。
32 位下 MongoDB 有 2G 的限制。
数据量大了后,MongoDB 内存要求会上去,它的设计哲学是尽量把 index 等放内存。不足时自动用系统调度 swap(nmap)。这个时候就等着 otm 然后被系统干掉。在 512 的 VPS 上,插十万条数据然后做几个 mapreduce 就能搞出这问题来。另外楼上有朋友说的没错,它是有多少内存用多少的。
resque 可以用 hirefire 这样的库来即使 fork,省的大部分时间空占内存。
关键在于可能的话还是多加些内存:)
nginx+Passenger 和 ExtJS 实在是合不来,本来意图就是用哪里加载哪里,结果 nginx 给全绑一块儿了,研究了半天也不会取消,速度慢且不说,还有一堆重复加载的问题,实在是怕了它了 还是用了 Thin,世界清静了……效率什么的再说吧=_=