Тут по сути 3 проблемы:
1) Похоже, что Unicorn постоянно заполняет всю оперативную память, из-за чего я вручную удаляю воркеры.
2) Единорог по какой-то причине порождает дополнительных рабочих, хотя я указал фиксированное количество рабочих (7 из них). Это частично вызывает накопление оперативной памяти, что также заставляет меня вручную удалять рабочие процессы.
3) Развертывание с нулевым временем простоя в моем случае ненадежно. Иногда он подхватывает изменения, иногда я получаю тайм-ауты шлюза. Каждое развертывание становится очень стрессовой ситуацией.
Мне не очень нравится использовать Monit, потому что он убивает воркеров, не дожидаясь, пока воркеры закончат обслуживание своих запросов.
Итак, это нормально? Есть ли у других людей, использующих Unicorn, ту же проблему, когда оперативная память просто неконтролируемо растет?
А также, где рабочие количество созданных рабочих не соответствует количеству определенных рабочих?
Другой альтернативой является убийца рабочих-единорогов, которую я бы попробовал после прочтения Unicorn Eating Memory.
Небольшое обновление:
Так что дошло до того, что New Relic сообщила мне, что память почти 95%. Поэтому мне пришлось убить рабочего. Интересно, что убийство этого работника значительно уменьшило объем памяти, как видно из графика ниже.
Что случилось с этим?
Для справки, вот мои unicorn.rb
и unicorn_init.sh
. Хотелось бы, чтобы кто-нибудь сказал мне, что где-то там ошибка.
unicorn.rb
root = "/home/deployer/apps/myapp/current"
working_directory root
pid "#{root}/tmp/pids/unicorn.pid"
stderr_path "#{root}/log/unicorn.stderr.log"
stdout_path "#{root}/log/unicorn.log"
listen "/tmp/unicorn.myapp.sock"
worker_processes 7
timeout 30
preload_app true
before_exec do |_|
ENV["BUNDLE_GEMFILE"] = '/home/deployer/apps/myapp/current/Gemfile'
end
before_fork do |server, worker|
# Disconnect since the database connection will not carry over
if defined? ActiveRecord::Base
ActiveRecord::Base.connection.disconnect!
end
old_pid = "#{root}/tmp/pids/unicorn.pid.oldbin`"
if old_pid != server.pid
begin
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
Process.kill(sig, File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
end
end
sleep 1
end
after_fork do |server, worker|
# Start up the database connection again in the worker
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
Redis.current.quit
Rails.cache.reconnect
end
unicorn_init.sh
#!/bin/sh
set -e
# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/deployer/apps/myapp/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="cd $APP_ROOT; BUNDLE_GEMFILE=/home/deployer/apps/myapp/current/Gemfile bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
AS_USER=deployer
set -u
OLD_PIN="$PID.oldbin"
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $OLD_PIN && kill -$1 `cat $OLD_PIN`
}
run () {
if [ "$(id -un)" = "$AS_USER" ]; then
eval $1
else
su -c "$1" - $AS_USER
fi
}
case "$1" in
start)
sig 0 && echo >&2 "Already running" && exit 0
run "$CMD"
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig USR2 && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
run "$CMD"
;;
upgrade)
if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
then
n=$TIMEOUT
while test -s $OLD_PIN && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $OLD_PIN
then
echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
run "$CMD"
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac