2013-08-10 3 views
14

New Relic Process snapshotUnicorn Wykorzystanie pamięci napełniania prawie cały RAM

Zasadniczo istnieją trzy problemy:

1) Unicorn wydaje się być stale zapełniać cały RAM, powodując mnie, aby usunąć ręcznie pracowników.

2) Jednorożec wydaje się być źródłem dodatkowych pracowników z jakiegoś powodu, chociaż określiłem określoną liczbę pracowników (7 z nich). To częściowo powoduje gromadzenie się pamięci RAM, co również powoduje ręczne usuwanie pracowników.

3) Wdrożenie z zerowym czasem przestoju jest nierealne w moim przypadku. Czasami odbiera zmiany, czasami dostaję limity czasu bramki. Każde wdrożenie staje się bardzo stresującą sytuacją.

Nie lubię używać Monita, ponieważ zabija pracowników bez czekania, aż pracownicy skończą wypełniać ich prośby.

Czy to normalne? Czy inne osoby, które używają Unicorn, mają ten sam problem, gdy pamięć RAM po prostu rośnie niekontrolowanie?

A także skąd pracownicy, ilu pracowników zrodziło się, nie zgadzają się z określoną liczbą pracowników?

Inną alternatywą jest zabójca robotnik jednorożca, który wypróbowałbym po przeczytaniu Unicorn Eating Memory.

Tiny Aktualizacja:

enter image description here

Więc przyszło do punktu, w którym New Relic mówił mi pamięć była prawie 95%. Więc musiałem zabić pracownika. Interesujące jest to, że zabicie tego pracownika znacznie zmniejszyło pamięć, co widać z poniższego wykresu.

Co z tym?

Dla porównania, oto moje unicorn.rb i unicorn_init.sh. Chciałbym, żeby ktoś mi powiedział, że gdzieś tam jest jakiś błąd.

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 
+0

jaki sposób można wygenerować ten wykres? – ant

Odpowiedz

9

Wygląda na to mają dwa problemy: 1) Masz błędy w koordynacji wdzięku restartu powodując starych jednorożca pracowników i stary mistrz trzymać się; 2) Twoja aplikacja (nie jednorożec) to przeciekająca pamięć.

Dla tego pierwszego, patrząc na kodzie before_fork, wydaje się, że używasz podejście pamięci-ograniczającym od the example config jednak masz literówkę w nazwie pliku .oldbin (czy dodatkowy back-kleszcz na końcu), co oznacza, nigdy nie sygnalizujesz starego procesu, ponieważ nie możesz odczytać pid z nieistniejącego pliku.

Na później, będziesz musiał zbadać i wywiercić.Spójrz w swojej aplikacji na buforowanie semantyki, która gromadzi dane w czasie; dokładnie sprawdź wszystkie użycie globali, klas-vars i vars klasy-klasy, które mogą zachować odniesienia do danych od żądania do żądania. Uruchom niektóre profile pamięci, aby scharakteryzować wykorzystanie pamięci. Możesz złagodzić wycieki pamięci, zabijając pracowników, gdy osiągną one większy górny limit; unicorn-worker-killer ułatwia to.

+0

Dzięki! Naprawię to od razu i zgłoś się. Doceń czas. –

+0

Czy ten jednorożec-pracownik-zabójca może być używany komercyjnie? – tulio84z

+0

@dbenhur można wyjaśnić nieco dalej, co "graceful restart" i to podejście ograniczające pamięć jest? – tulio84z

3

Zastosowanie unicorn-worker-killer, to sprawia, że ​​łatwiej jest zabić pracowników, którzy zużywają dużo RAM :)