2013-05-06 12 views
24

Ryan Bates wspomina o funkcji LISTEN/NOTIFY Postgresa podczas omawiania powiadomień push w this episode, ale nie byłem w stanie znaleźć żadnej wskazówki, jak zaimplementować LISTEN/NOTIFY w mojej aplikacji rails.postgres LISTEN/NOTIFY rails

Oto dokumentacja funkcji wait_for_notify wewnątrz adaptera pg, ale nie mogę określić, do czego dokładnie służy/jest przeznaczona.

Czy musimy pobierać bezpośrednio do zmiennej connection adaptera pg?

+0

Jeśli dobrze rozumiem, chcesz db do przekazywania informacji do szyn. Jeśli szyny mają jakiegoś rodzaju słuchacza, można to zrobić za pomocą plperl, który następnie łączy się z takim słuchaczem i dostarcza informacji. –

+0

Próbuję znaleźć przykładowy kod, jak skonfigurować szyny do działania jako ten słuchacz. Nie sądzę, że potrzebujemy plperl, ze względu na LISTEN/NOTIFY zdolności PostgreSzu, ale jestem grą, aby wypróbować wszystko, co możesz wymyślić –

+0

Miałem na myśli hack; lepiej spójrz na http://sequel.rubyforge.org/rdoc/files/doc/postgresql_rdoc.html lub https://github.com/taotetek/listen_notify_poller/blob/master/example.rb –

Odpowiedz

46

Patrzysz w odpowiednim miejscu z metodą wait_for_notify, ale ponieważ ActiveRecord najwyraźniej nie dostarcza API dla go używać, trzeba uzyskać co bazowego PG :: obiektu Connection (lub jeden z nich , jeśli używasz konfiguracji wielowątkowej), której ActiveRecord używa do komunikowania się z Postgresem.

Po uzyskaniu połączenia po prostu wykonaj wszystkie potrzebne instrukcje, a następnie przekaż blok (i opcjonalny okres czasu) na wait_for_notify. Zauważ, że spowoduje to zablokowanie bieżącego wątku i zmonopolizowanie połączenia PostgreS, aż do osiągnięcia limitu czasu lub pojawienia się NOTIFY (więc nie będziesz chciał tego robić na przykład w żądaniu internetowym). Kiedy inny proces wyda numer NOTIFY na jednym z kanałów, którego słuchasz, blok zostanie wywołany z trzema argumentami - kanałem, który powiadomił, pid z PostgreS backend, który uruchomił NOTIFY, i ładunek, który towarzyszył NOTIFY (Jeśli w ogóle).

Nie używałem ActiveRecord na dłuższą chwilę, więc nie może być czystszy sposób, aby to zrobić, ale to wydaje się działać dobrze w 4.0.0.beta1:

# Be sure to check out a connection, so we stay thread-safe. 
ActiveRecord::Base.connection_pool.with_connection do |connection| 
    # connection is the ActiveRecord::ConnectionAdapters::PostgreSQLAdapter object 
    conn = connection.instance_variable_get(:@connection) 
    # conn is the underlying PG::Connection object, and exposes #wait_for_notify 

    begin 
    conn.async_exec "LISTEN channel1" 
    conn.async_exec "LISTEN channel2" 

    # This will block until a NOTIFY is issued on one of these two channels. 
    conn.wait_for_notify do |channel, pid, payload| 
     puts "Received a NOTIFY on channel #{channel}" 
     puts "from PG backend #{pid}" 
     puts "saying #{payload}" 
    end 

    # Note that you'll need to call wait_for_notify again if you want to pick 
    # up further notifications. This time, bail out if we don't get a 
    # notification within half a second. 
    conn.wait_for_notify(0.5) do |channel, pid, payload| 
     puts "Received a second NOTIFY on channel #{channel}" 
     puts "from PG backend #{pid}" 
     puts "saying #{payload}" 
    end 
    ensure 
    # Don't want the connection to still be listening once we return 
    # it to the pool - could result in weird behavior for the next 
    # thread to check it out. 
    conn.async_exec "UNLISTEN *" 
    end 
end 

Na przykład bardziej ogólne użycie, patrz Sequel's implementation.

Edytuj, aby dodać: Oto kolejny opis tego, co się dzieje. Może to nie być dokładna implementacja za kulisami, ale wydaje się, że wystarczająco dobrze opisuje to zachowanie.

Postgres przechowuje listę powiadomień dla każdego połączenia. Kiedy używasz połączenia do wykonania LISTEN channel_name, mówisz Postgresowi, że wszelkie powiadomienia na tym kanale powinny zostać przekazane na listę tego połączenia (wiele połączeń może nasłuchiwać na tym samym kanale, dzięki czemu pojedyncze powiadomienie może zostać przekazane na wiele list) . Połączenie może LISTEN do wielu kanałów w tym samym czasie, a powiadomienia do któregokolwiek z nich zostaną przekazane na tę samą listę.

Co to jest jest wysyłane najstarsze powiadomienie z listy połączeń i przekazuje informacje do bloku - lub, jeśli lista jest pusta, śpi, aż powiadomienie stanie się dostępne i robi to samo dla tego (lub do upływu czasu zostanie osiągnięty, w takim przypadku po prostu zwraca zero). Ponieważ wait_for_notify obsługuje tylko jedno powiadomienie, będziesz musiał wielokrotnie go wywoływać, jeśli chcesz obsłużyć wiele powiadomień.

Kiedy UNLISTEN channel_name lub UNLISTEN * Postgres zatrzyma popychanie tych powiadomień do listy połączeniem, ale te, które już zostały zepchnięte na tej liście pozostanie tam, i będzie wait_for_notify jeszcze zwrócić je, kiedy jest następny nazywa. Może to spowodować problem z powiadomieniami, które są gromadzone po wait_for_notify, ale przed UNLISTEN pozostają w pobliżu i są nadal obecne, gdy inny wątek sprawdzi to połączenie.W takim przypadku po UNLISTEN możesz wywołać wait_for_notify z krótkimi limitami czasu, aż zwróci zero. Ale chyba że intensywnie korzystasz z LISTEN i NOTIFY dla wielu różnych celów, jednak prawdopodobnie nie warto się tym martwić.

Dodałem lepszy link do implementacji Sequel powyżej, polecam, patrząc na to. To całkiem proste.

+0

Tylko dla wyjaśnienia: jeśli nie ma Odpowiedź na pytanie w 1/2 sekundy, czy przestaje słuchać? –

+0

Jeśli nie otrzyma odpowiedzi po określonym limicie czasu, wait_for_notify zwróci zero, a blok nie zostanie wywołany, ale LISTEN dla kanału1 i kanału2 pozostanie w mocy do momentu UNLISTEN. Twój kod może więc pójść za cokolwiek innego, a kiedy ponownie zaczekasz na potwierdzenie, nadal będziesz słuchał kanału 1 i kanału 2. Wierzę, że jeśli NOTIFY wystąpił, gdy nie blokowałeś wait_for_notify, od razu go odbierzesz, gdy ponownie zaczekasz, aby ponownie zaaplikować, ale nie jestem tego pewien. – PreciousBodilyFluids

+0

Musimy więc odpytać 'wait_for_notify', aby rzeczywiście otrzymać wiadomość? –