Thin, unix sockets, Rails i Merb
Posted by 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.sockZostanie 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: trueOpcja -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
endNależ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 '|'}}"
endW 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.ymlTeraz 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.


Kanały IRC![[Dilber w Onecie]](/images/larry.png)


ladny artykulik, dzeki
na prawdę przydatna zabawa. dobra robota. swoją drogą zastanawiam się kiedy rosjanie skończą te persistent_connections..
“So that’s why Thin new release (0.7.0 codename Spherical Cow) supports persistent connections (aka Keep-Alive).”
czyli już są ;}
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.
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.
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!
@rsz: Najwyraźniej są szybsze, zobacz ten tekst.
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ą