Search
Write a publication
Pull to refresh

Unicorn


Что это?



Unicorn – это HTTP сервер для Ruby, подобный Mongrel или Thin. Он использует Ragel HTTP парсер от Mongrel, но разительно отличается по архитектуре и философии.



В классической конфигурации у вас есть nginx, посылающий запросы пулу Mongrel-ов используя умный балансировщик или просто раунд робин.



В конечном счете вы хотите лучшую наглядность и надежность, используя балансировщик нагрузки, поэтому можно запустить hadproxy по такой схеме:





Который работает прекрасно. Мы использовали эту конфигурацию долгое время и были довольны. Однако, существовало несколько проблем.




Медленные action


Когда action выполняется более 60 секунд, Mongrel пытается завершить поток. Это оказалось ненадежным из-за потоков Ruby. Mongrel часто зависает и его приходится приходится убивать с помощью внешних процессов (напр. god или monit).



Конечно, это проблема вашего приложения. Action не должен занимать более 60 секунд. Но у нас сложное приложение, с множеством мобильных частей и плохие вещи случаются. Наша рабочая (production) среда нуждается в хорошем управлении ошибок и отказов.



Потребление памяти


Мы перезагружаем Mongrel при определенном пороге потребления памяти. Это частая проблема с некоторыми нашими приложениями.



Как и медленные действия, это случается. Вы должны быть готовы к тому, что не все идеально и так же строить свою рабочую среду. Мы не так часто убиваем процессы приложений из-за потребления памяти, но все же это случается.



Медленное развертывание


Когда ваш процессор нагружен, перезагрузка 9 mongrel-ов – страшная вещь. Каждый из них начинает загружать Rails, все ваши gems, все ваши библиотеки и ваше приложение в память, прежде чем будет способно обслуживать запросы. Они все делают одно и тоже, но, в то же время, борются за ресурсы.



В это время, когда вы убили все mongrel-ы, все пользователи отключаются от вашего сайта и ожидают полной его загрузки. Если вы действительно перегружены, это может потребовать больше 10 секунд ожидания. Ох.



Есть некоторые сложные решения, для автоматизации поочередной перезагрузки (rolling restarts) с несколькими hadproxy и рестартом mongrel-ов в нескольких пулах. Но, как я уже сказал, это сложно и ненадежно.



Медленные рестарты


Как и в случае развертывания и временем на завершение mongrel-ов, вызванное потреблением памяти или таймаутом, это может занять довольно много времени, прежде чем сервер будет способен снова обслуживать запросы. Во время пиковой загрузки это может существенно сказаться на отзывчивости сайта.



Push Balancing


В большинстве популярных решениях балансировки нагрузки, запрос обрабатывается балансировщиком, который решает, какой из mongrel-ов обслужит его. Лучший балансировщик умен на столько, что понимает, какой из процессов свободен.



Именно по этому вы должны перейти с решения, основанном на nginx к hadproxy, который лучше организует очередь и передает запрос mongrel-у, который и выполняет его.



В конце концов, балансировщик передает запрос mongrel-у. И вот тут возникает риск, когда запрос может быть передан mongrel-у, который может быть не лучшим кандидатом на его обработку.



Unicorn



Unicorn имеет немного другую архитектуру. Вместо nginx => hadproxy => mongrel конфигурации, вы имеете что-то вроде этого:





nginx посылает запросы напрямую к пулу Unicorn worker через Unix Domain Socket (или TCP, если вам так привычнее). Unicorn master управляет воркерами, в то время как OS управляет балансировкой, о которой мы поговорим. Master как таковой никогда не видит запросы.



Вот разница в конфигурации между nginx => hadproxy и nginx => unicorn:



# port 3000 is haproxy
upstream github {
    server 127.0.0.1:3000;
}


# unicorn master opens a unix domain socket
upstream github {
    server unix:/data/github/current/tmp/sockets/unicorn.sock;
}


Когда Unicorn master загружается, он загружает ваше приложение в память. Как только оно готово к обслуживанию запросов, оно разветвляется на 16 воркеров. Эти воркеры затем подключаются к сокету только в том случае, если способны обработать запрос. В этом случае ядро управляет нагрузкой для нас.



Медленные actions


Unicorn master точно знает, сколько каждый воркер обрабатывает запрос. Если воркер обрабатывает его больше 30 секунд (мы понизили это значение, со значения по умолчанию в Mongrel), master немедленно его прекращает и образует еще один. Новый воркер сразу готов к новым запросам – больше нету многосекундной задержки.



Когда это случается, клиенту посылается страница с ошибкой 502. Возможно вы ее даже видели и удивлялись, что же произошло. Скорее всего это означает, что запрос был завершен прежде чем был выполнен.



Потребление памяти


Когда воркер начинает потреблять слишком много памяти, god или monit может послать ему сигнал QUIT. Это завершает воркер после обработки текущего запроса. Как только воркер завершается, мастер запускает новый, который сразу же готов обрабатывать новые запросы. В этом случае нам не требуется рвать соединение посреди запроса или испытывать неудобство при загрузке нового.



Медленное развертывание


Совмещенный с нашими рецептами Capistrano, развертывание очень быстро. Вот что мы делаем.



Сначала мы посылаем существующему Unicorn master USR2 сигнал. Он сообщает, что нужно начать новый master процесс, перезагрузить все наше приложение. Когда новый master полностью загружен, он запускает все воркеры, какие требуются. Первый воркер узнает, что запущен старый master и посылает ему сигнал QUIT.



Когда старый master получает этот сигнал, он запускает полное завершение его воркеров. Как только воркеры заканчивают обслуживать запросы, они завершаются. На данный момент у нас есть свежая версия нашего приложения, полностью загруженная и готовая получать запросы, без какого бы то ни было простоя: и старые, и новые воркеры полностью доступны в Unix Domain Socket, поэтому nginx не должен заботиться о переходе.



Так же мы можем использовать этот процесс для обновления самого Unicorn.



А что насчет миграций? Просто запустите страницу “Этот сайт находится на временном обслуживании”, запустите миграцию, перезагрузите Unicorn и, затем, удалите временную страницу. Просто как ни когда.



Медленные перезагрузки


Как упоминалось выше, перезагрузки продолжительны только тогда, когда загружается master. Воркеры могут быть завершены и перезапущены удивительно быстро.



Когда мы делаем полный рестарт, только один процесс загружает все наше приложение – master. Он не растрачивает ресурсы.



Push Balancing


Вместо раздачи запросов, воркеры забирают их. По существу, воркеры просто спрашивают о новом запросе, когда они готовы его выполнить. Все просто.



Стратегия миграции



Итак, вы хотите мигрировать с thin или mongrel cluster на Unicorn? Если вы используете nginx => hadproxy => cluster, то все просто. Вместо изменения настроек, вы просто можете сказать воркерам, что бы они слушали TCP порт, где они были порождены. Эти порты могут совпадать с портами текущего Mongrel.



Сверьтесь с документацией о конфигурации для примера этого метода, особенно эту часть:



after_fork do |server, worker|
  # per-process listener ports for debugging/admin/migrations
  addr = "127.0.0.1:#{9293 + worker.nr}"
  server.listen(addr, :tries => -1:delay => 5:tcp_nopush => true)
end


Он сообщает каждому воркеру о том, что нужно слушать порт, равный его номеру + 9293 – он будет пытаться его использовать, пока порт не станет свободным.



Используя этот фокус, вы можете запустить пул Unicorn воркеров, затем завершить ваш текущий пул Mongrel или thin вашего приложения, когда Unicorn будет готов. Воркеры будут доступны на порту как только это станет возможным и будут готовы обрабатывать запросы.



Это хороший способ получить близкую к Unicorn конфигурацию, не затрагивая настроек hadproxy или nginx.



(ради шутки, попробуйте запустить “kill -9” на воркере, а затем “ps aux”. Вы, возможно, даже и не поймете, что он исчез.



Как только вы начнете чувствовать себя уверенно с Unicorn и у вас будет готов скрипт для развертывания, вы можете изменить nginx upstream для использования Unix Domain Sockets и затем освободить поты в Unicorn воркерах. Hadproxy больше не нужен.



Настройки GitHub



Вот наш Unicorn конфиг:



# unicorn_rails -c /data/github/current/config/unicorn.rb -E production -D
 
rails_env = ENV['RAILS_ENV'] || 'production'
 
# 16 workers and 1 master
worker_processes (rails_env == 'production' ? 16 : 4)
 
# Load rails+github.git into the master before forking workers
# for super-fast worker spawn times
preload_app true
 
# Restart any workers that haven't responded in 30 seconds
timeout 30
 
# Listen on a Unix data socket
listen '/data/github/current/tmp/sockets/unicorn.sock':backlog => 2048
 
 
##
# REE
 
# http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
if GC.respond_to?(:copy_on_write_friendly=)
  GC.copy_on_write_friendly = true
end
 
 
before_fork do |server, worker|
  ##
  # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
  # immediately start loading up a new version of itself (loaded with a new
  # version of our app). When this new Unicorn is completely loaded
  # it will begin spawning workers. The first worker spawned will check to
  # see if an .oldbin pidfile exists. If so, this means we've just booted up
  # a new Unicorn and need to tell the old one that it can now die. To do so
  # we send it a QUIT.
  #
  # Using this method we get 0 downtime deploys.
 
  old_pid = RAILS_ROOT + '/tmp/pids/unicorn.pid.oldbin'
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT"File.read(old_pid).to_i)
    rescue Errno::ENOENTErrno::ESRCH
      # someone else did our job for us
    end
  end
end
 
 
after_fork do |server, worker|
  ##
  # Unicorn master loads the app then forks off workers - because of the way
  # Unix forking works, we need to make sure we aren't using any of the parent's
  # sockets, e.g. db connection
 
  ActiveRecord::Base.establish_connection
  CHIMNEY.client.connect_to_server
  # Redis and Memcached would go here but their connections are established
  # on demand, so the master never opens a socket
 
 
  ##
  # Unicorn master is started as root, which is fine, but let's
  # drop the workers to git:git
 
  begin
    uid, gid = Process.euidProcess.egid
    user, group = 'git''git'
    target_uid = Etc.getpwnam(user).uid
    target_gid = Etc.getgrnam(group).gid
    worker.tmp.chown(target_uid, target_gid)
    if uid != target_uid || gid != target_gid
      Process.initgroups(user, target_gid)
      Process::GID.change_privilege(target_gid)
      Process::UID.change_privilege(target_uid)
    end
  rescue => e
    if RAILS_ENV == 'development'
      STDERR.puts "couldn't change user, oh well"
    else
      raise e
    end
  end
end
 


Я бы посоветовал вам сделать страницу документации SIGNALS вашей домашней страницей и читать все остальные разделы, доступные на сайте Unicorn. Он очень хорошо документирован. И Эрик старается его улучшать каждый день.



Скорость



Если честно, то я не беспокоюсь об этом. Я хочу такую среду, которая бы хорошо разбиралась с тем хаосом. Мне нужна стабильность и надежность больше, чем чистая скорость.



К счастью, Unicorn достоин по обоим парамтерам.



Вот тесты Тома для нашего железа на Rackspace. Мы запустили GitHub на одной машине, и бенчмарк на другой. Сервера имели 8 ядер и 16GB памяти, соедененные через гигабитный ethernet.



Мы тестировали простой Rails action, который выдавал строку. Это означает, что каждый запрос проходил через серию Rails роутингов и все такое прочее.



У Mongrel был hadproxy перед ним. Unicorn-tcp использовал порт, открытый master-ом, unicorn-unix с 1024 backlog использовал master с открытым unix domain socket по умолчанию, а 2048 backlog использовал повышенный “listen”.



Этот бенчмарк генерировал множество запросов до тех пор, пока не появлялась ошибка 502 или 500. В каждом тесте работало 8 воркеров.



mongrel
 8: Reply rate [replies/s]:
          min 1270.4 avg 1301.7 max 1359.7 stddev 50.3 (3 samples)
unicorn-tcp
 8: Reply rate [replies/s]:
          min 1341.7 avg 1351.0 max 1360.7 stddev 7.8 (4 samples)
unicorn-unix (1024 backlog)
 8: Reply rate [replies/s]:
          min 1148.2 avg 1149.7 max 1152.1 stddev 1.8 (4 samples)
unicorn-unix (2048 backlog)
 8: Reply rate [replies/s]:
          min 1462.2 avg 1502.3 max 1538.7 stddev 39.6 (4 samples)


Заключение



Passenger, Mongrel и Thin замечательны.



Используйте то, что лучше для вас. Решите то, что вам нужно и решайте насущные проблемы. Не используйте это только потому, что GitHub использует это. Используйте то, что решает ваши проблемы.



Мы используем Thin для обслуживания GitHub Services, а так же я использую Passenger для множества собственных проектов. Unicorn не для каждого приложения. Но он работает прекрасно для нас.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.