1. 介绍

unicorn是一个运行ruby应用的HTTP服务器。当你写完了ruby应用,准备部署时,就可以用它来运行ruby应用。但一般来说,我们不会直接那样用,我们会结合nginx来用,把高性能的nginx暴露在最外层,而反向代理到unicorn。nginx是一个静态web服务器,它是部署在80端口,一般来说,它会处理所有的静态文件请求,比如css,js等,假如需要动态的内容,它就会转发给unicorn,而unicorn做一些处理,比如查数据库等,处理完,再转交给nginx,最后nginx发送给用户,所以整个过程都是nginx在起一个缓冲和保护的作用,用户并没有感觉到unicorn的存在。

这种处理方式跟apache有很大的不同,比如说处理php的mod_php模块。apache也是跟nginx一样,是个静态服务器。它本身不能解析php解析,要解析php脚本,就必须得装php解析的组件,可以把这组件编译进apache中,也可以以动态加载的形式来加载。但无论怎样,这个模块本身就会在处理php请求时成为apache的一部分,处理的效率和性能就取决于这个模块本身。而nginx不一样,它处理ruby动态的请求和静态文件的请求是分开的,静态文件它自己能处理,而动态的,它通过反向代理来转发,这两部分本身就分开了,这样就很灵活了,因为nginx本身性能高,能起到保护的作用,而后面的unicorn进程也有自己的空间,可以尽量发挥自己的功能和作用。比如,unicorn也有类似于nginx的master-worker的进程模式,这样可以部署多个unicorn形成负载均衡。unicorn本身也是一个gem,这样就不用像mod_php那样编译或安装nginx的组件,这样很灵活,假如以后要把unicorn换成puma,也是很方便的事。

unicorn还支持copy-on-write-friendly(写时复制),高效利用内存。还支持监听到unix socket等功能。

unicorn是多进程的处理请求的模式,而且它还是预先folk(prefork)。也就是说,在启动服务的时候就会先创建好进程。一般来说,假如你有两个核心的cput,那就会创建一个master进程,两个worker进程,请求进来了,由master来分配给worker处理,这点跟nginx的机制差不多。

2. 使用

我们会使用mina结合nginx来部署unicorn应用。

我们需要先把unicorn进程给跑起来,而它是以监听unix socket的方式跑的,而nginx也是把请求转发到unicorn的unix socket,unix socket是一种套接字,先把它想象成一个文件。

用mina来部署unicorn进程需要一个gem,叫mina-unicorn

2.1 mina-unicorn

unicorn本身也是一个linux的命令行程序,而mina-unicorn就是根据unicorn命令结合linux的kill命令来封装的。它的源码也是比较简单的。

在Gemfile文件中添加下面一行。

gem 'mina-unicorn', :require => false
gem 'unicorn'

执行bundle安装。

添加下面一行到config/deploy.rb文件中。

require 'mina/unicorn'

设置socket和pid文件放置的目录位置。

socket是unicorn监听的文件套接字,而pid是一个进程标识符,是个整数,linux一般用它配合kill指令来控制进程的启动,重启等,比如"kill cat /tmp/unicorn.pid"。

# socket文件和pid文件会放在下面的目录中,这个可以查看源码获知详情。
set :shared_paths, ['tmp/sockets', 'tmp/pids']

找到config/deploy.rbtask :setup => :environment的部分,添加下面两段。

task :setup => :environment do
  ...

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/tmp/sockets"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/tmp/sockets"]

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/tmp/pids"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/tmp/pids"]

  ...
end

然后运行下面的命令。

mina setup

这个作用是在部署目录下的shared目录下生成tmp/socketstmp/pids目录。

做完这些,还差一步,就是需要部署完应用的时候,重启unicorn。

config/deploy.rb文件中找到task :deploy => :environment部分添加invoke :'unicorn:restart'这一行,如下所示。

desc "Deploys the current version to the server."
task :deploy => :environment do
  to :before_hook do

  end
  deploy do
   ...

    to :launch do
      invoke :'unicorn:restart'
      ...
    end
  end
end

在config目录下新增一个文件叫unicorn.rb,这个是unicorn的配置文件,内容如下:

app_path = File.expand_path( File.join(File.dirname(__FILE__), '..', '..'))
worker_processes   1
timeout            180
listen             "#{app_path}/shared/tmp/sockets/unicorn.sock"
pid                "#{app_path}/shared/tmp/pids/unicorn.pid"
stderr_path        "log/unicorn.log"
stdout_path        "log/unicorn.log"

before_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{app_path}/current/Gemfile"
end

其中worker_processes跟你主机的cpu核数保持一致就好了。其他的不用改。官方也提供了样板文件,我也是参考样板文件作修改的。

现在就可以部署了。

mina deploy

现在可以进入部署的主机,查看部署目录下的shared目录下的tmp/sockets目录是不是生成了unicorn.sock文件。

2.2 nginx

现在unicorn跑起来,还需要一个接口把nginx反向代理到unicorn,这个通过配置nginx就好了。

/etc/nginx/conf.d下新增一个配置文件,比如叫rails.conf。

upstream rails365 {
    # Path to Unicorn SOCK file, as defined previously
    server unix:///home/yinsigan/rails365/shared/tmp/sockets/unicorn.sock fail_timeout=0;
}
server {
    listen 80 default_server;
    server_name www.rails365.net;
    root         /home/yinsigan/rails365/current/public;
    keepalive_timeout 70;

    location ~ ^/assets/ {
       gzip_static on;
       expires max;
       add_header Cache-Control public;
    }

        try_files $uri/index.html $uri @rails365;
    location @rails365 {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://rails365;
    }

    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

}

server {
  listen 80;
  server_name rails365.net;

  return 301 $scheme://www.rails365.net$request_uri;
}

关键是反向代理的那部分,就是try_fileslocation @rails365还有upstream rails365这几个地方。

我部署到的目录是/home/yinsigan/rails365root,还有socket的位置都要指向正确的。

最后执行以下命令让配置生效。

sudo nginx -s reload

现在可以尝试访问你的网站看是否生效了。

另外,关于nginx的配置可以查看本站的相关文档

完结。