Thin, unix sockets, Rails i Merb

Opublikowane przez Jarosław Zabiełło Mon, 03 Mar 2008 02:36:00 GMT

Thin to szybki serwer HTTP służący do uruchamiania webowych frameworków napisanych w języku Ruby. Najbardziej z nich popularny jest oczywiście Rails. Choć istnieje wiele sposobów uruchomienia Railsów na serwerze produkcyjnym, użycie Thin wydaje się aktualnie najlepszym rozwiązaniem z przynajmniej dwóch powodów: zajmuje mniej pamięci i jest szybszy od popularnego Mongrela. Thin wykorzystuje dwa bardzo szybkie moduły: Mongel parser, Event Machine oraz (wzorowaną na pythonowym WSGI) bibliotekę Rack.

Od jakiegoś czasu Thin udostępnia uruchamianie klastra procesów wykorzystujący szybsze od TCP, sockety uniksowe. Zaletą jest nie tylko większa szybkość pracy, ale także odpada problem rezerwacji odpowiedniej liczby portów TCP.

Wygenerowanie pliku konfiguracyjnego polega na uruchomieniu w katalogu z projektem Rails (lub Merb) komendy:

thin config -C config/thin.yml -s4 -e production \
  --socket /tmp/thin_myapp.sock

Zostanie wygenerowany plik konfiguracyjny o mniej więcej takiej treści:

--- 
pid: tmp/pids/thin.pid
socket: /tmp/thin_myapp.sock
log: log/thin.log
max_conns: 1024
timeout: 30
chdir: /home/app/rails/myapp
environment: production
max_persistent_conns: 512
servers: 4
daemonize: true

Opcja -s4 oznacza że chcemy użyć 4 procesy. (W wypadku Nginksa, trzeba uważać na opcję max_persistent_conns, bo Nginx zdaje się nie ma jeszcze zaimplementowanej obsługi persistent connections. Najlepiej ją wyłączyć, tzn. przypisać jej wartość równą zeru. Apache powinien to obsługiwać. Z persistent connections wszystko powinno działać trochę szybciej.)

Aby to uruchomić, warto stworzyć plik startowy. Dla Debiana/Ubuntu należy stworzyć plik /etc/init.d/thin.rb (dla wygody napisalem to w Ruby zamiast Bashu):

#!/usr/bin/env ruby

require 'fileutils'
include FileUtils

def pids
  Dir.glob('tmp/pids/thin*.pid').map do |entry| 
    File.open(entry).read
  end
end

def start
  if pids.empty?
    rm_f "tmp/pids/thin*.pid"
    if File.exists? "config/init.rb" # start Merb
      system "thin start -r config.ru -C config/thin.yml"
    else # start Rails
      system "thin start -C config/thin.yml"
    end
  else
    puts "Thin cluster is already working"
  end
end

def stop
  if pids.empty?
    puts "Thin cluster is not working"
  else
    puts "Stopping thin cluster" unless pids.empty?
    pids.each {|pid| system "kill -TERM #{pid}" }
    rm_f "tmp/pids/thin*.pid"
  end
end

def restart
  stop
  sleep 1
  start
end

def status
  if pids.empty?
    puts "Thin cluster is not working"
  else
    puts "Thin cluster is working (pids: #{pids.join ', '})"
  end
end

if __FILE__ == $0
  opts = %w(start stop restart status)
  if opts.include?(ARGV[0]) and File.exists?(ARGV[1])
    Dir.chdir ARGV[1]
    eval ARGV[0]
  else
    print "Syntax: ruby {#{File.basename($0)} <#{opts.join '|'}}"
    puts  " /path/to/rails_or_merb/project"
  end
end

Należy dodać uprawnienia (sudo chmod a+x thin.rb) i ustawić aby domyślnie, podczas uruchomienia całego serwera był uruchamiany następujący skrypt (thin_myapp.rb) dla konkretnego projektu Rails lub Merb (komenda sudo update-rc.d -f thin_myapp.rb defaults).

#!/usr/bin/env ruby
path = '/path/to/rails-or-merb/project'

opts = %w(start stop restart status)
if opts.include? ARGV[0]
  system "/etc/init.d/thin.rb #{ARGV[0]} #{path}"
else
  puts "Syntax: #{File.basename(__FILE__)} {#{opts.join '|'}}"
end

W wypadku Merba trzeba pamiętać aby stworzyć dodatkowy plik konfiguracyjny config.ru (oczywiście dotyczy to Merba 0.9 lub nowszego):

# config.ru
Merb::BootLoader.run
run Merb::Rack::Application.new 

Wyżej podany plik startowy rozpoznaje czy projekt to Rails czy Merb na podstawie odnalezienia pliku config/init.rb. Ale jak ktoś chce odpalać to ręcznie to Merba korzystającego z Thina i jego plikiu konfiguracyjnego (config/thin.yml) odpala się w taki sposób:

thin start -r config.ru -C config/thin.yml

Teraz wystarczy tylko ustawić load balancing do tak uruchomionego serwera. Dla serwera Nginx będzie to

upstream thin_myapp {
  server unix:/tmp/thin_myapp.0.sock;
  server unix:/tmp/thin_myapp.1.sock;
  server unix:/tmp/thin_myapp.2.sock;
  server unix:/tmp/thin_myapp.3.sock;
}

I w definicji serwera wirtualnego:

if (!-f $request_filename) {
  proxy_pass http://thin_myapp;
  break;
}

Zobacz też:

Update 2008-03-04:

Poprawiłem skrypt startowy tak aby był bardziej uniwersalny w wypadku używania wielu projektów Rails (lub Merb). Dodałem też informacje o możliwym problemie ze zmienną max_persistent_conns w wypadku Nginksa.

Tagi , , , ,  | 8 comments

Comments

  1. Avatar comboy powiedział about 22 hours later:

    ladny artykulik, dzeki

  2. Avatar dmilith powiedział 2 days later:

    na prawdę przydatna zabawa. dobra robota. swoją drogą zastanawiam się kiedy rosjanie skończą te persistent_connections..

  3. Avatar dmilith powiedział 2 days later:

    “So that’s why Thin new release (0.7.0 codename Spherical Cow) supports persistent connections (aka Keep-Alive).”

    czyli już są ;}

  4. Avatar Jarosław Zabiełło powiedział 3 days later:

    Są dla Thin, nie ma dla Nginx’a. Zauważyłem też że te sockety uniksowe są trochę niestabilne (po jakimś czasie znikał mi procesy) Musiałem na razie przełączyć na porty TCP z którymi nie ma problemu.

  5. Avatar rsz powiedział 3 days later:

    Skąd info, że sockety unixowe są szybsze, niż TCPIP (lokalne)? Wg mnie we współczesnych implementacjach (nowsze Linuxy, *BSD) wydajność obydwu powinna być taka sama.

  6. Avatar Adam Byrtek powiedział 4 days later:

    Ostatnio przeprowadzałem kilka testów wydajnościowych, z których wynika, że konfiguracja Apache + 3 * FastCGI jest jakieś 10-20% bardziej wydajna niż Pound + 3 * Mongrel. Zastosowanie Nginx jako front-end serwera zwiększa moc obu konfiguracji, ale nie zmienia relacji między nimi.

    Więcej informacji o tym, jak mierzyć wydajność serwera HTTP: http://www.adambyrtek.net/2008/03/05/benchmarking-http-performance/

    Prawdopodobnie różnica wydajności wynika właśnie z zastosowania socketów zamiast połączeń TCP/IP – dlatego bardzo chętnie wypróbuję Thina!

  7. Avatar Jarosław Zabiełło powiedział 4 days later:

    @rsz: Najwyraźniej są szybsze, zobacz ten tekst.

  8. Avatar Jarosław Zabiełło powiedział 8 days later:
    Znalazłem informację, która wyjaśnia to, dlaczego miałem problemy z uniksowymi socketami. Thin 0.7.1 wymaga nowszego gema EventMachine 0.11.0. Można go zainstalować za pomocą
    gem install eventmachine --source http://code.macournoyer.com

(leave url/email »)

   Pomoc języka formatowania Obejrzyj komentarz