2013-05-06 11 views
24

Ryan Bates cita la funzionalità LISTEN/NOTIFY di Postgres quando si discutono le notifiche push in this episode, ma non sono riuscito a trovare alcun suggerimento su come implementare un LISTEN/NOTIFY nella mia app di rotaie.postgres ASCOLTA/NOTIFICA binari

Questa è la documentazione relativa alla funzione wait_for_notify all'interno dell'adattatore pg, ma non riesco a capire per cosa sia esattamente progettato/progettato.

È necessario toccare direttamente la variabile connection dell'adattatore pg?

+0

Se ho capito bene, volete db per inviare informazioni alle guide. Se rails ha una sorta di listener, potrebbe essere fatto con plperl che quindi si connette a tale listener e fornisce informazioni. –

+0

Sto cercando di trovare un codice di esempio su come impostare i binari per agire come ascoltatore. Non penso che abbiamo bisogno di plperl, a causa delle abilità LISTEN/NOTIFY di Postgres, ma sono partita per provare qualsiasi cosa tu possa pensare a –

+0

Ho avuto un hack in mente; meglio guardare http://sequel.rubyforge.org/rdoc/files/doc/postgresql_rdoc.html o https://github.com/taotetek/listen_notify_poller/blob/master/example.rb –

risposta

46

Stai cercando nel posto giusto con il metodo wait_for_notify, ma dal momento che ActiveRecord a quanto pare non fornisce un'API per utilizzarlo, devi raggiungere l'oggetto PG :: Connection sottostante (o uno di essi , se stai eseguendo una configurazione con multithreading che ActiveRecord sta usando per parlare con Postgres.

Una volta ottenuta la connessione, è sufficiente eseguire qualsiasi istruzione LISTEN necessaria, quindi passare un blocco (e un periodo di timeout opzionale) a wait_for_notify. Nota che questo bloccherà il thread corrente e monopolizzerà la connessione di Postgres, fino a quando non verrà raggiunto il timeout o si verificherà un NOTIFY (quindi non vorresti farlo all'interno di una richiesta web, per esempio). Quando un altro processo invia un NOTIFY su uno dei canali che stai ascoltando, il blocco verrà chiamato con tre argomenti: il canale che ha ricevuto la notifica, il pid del backend Postgres che ha attivato lo NOTIFY e il payload che ha accompagnato lo NOTIFY. (se presente).

Non ho usato ActiveRecord in un bel po ', quindi ci può essere un modo più pulito per fare questo, ma questo sembra funzionare bene in 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 

Per un esempio di un uso più generale, vedere Sequel's implementation.

Modifica per aggiungere: Ecco un'altra descrizione di cosa sta succedendo. Questa potrebbe non essere l'esatta implementazione dietro le quinte, ma sembra descrivere abbastanza bene il comportamento.

Postgres mantiene un elenco di notifiche per ciascuna connessione. Quando usi una connessione per eseguire LISTEN channel_name, stai dicendo a Postgres che qualsiasi notifica su quel canale dovrebbe essere spinta alla lista di questa connessione (più connessioni possono ascoltare lo stesso canale, quindi una singola notifica può finire per essere spinta in molti elenchi) . Una connessione può LISTEN in molti canali allo stesso tempo e tutte le notifiche a qualsiasi di esse verranno inviate allo stesso elenco.

Quello che wait_for_notify compila è la notifica meno recente dell'elenco della connessione e passa le sue informazioni al blocco - oppure, se l'elenco è vuoto, rimane in attesa fino a quando non viene resa disponibile una notifica e fa lo stesso (o fino al timeout è raggiunto, nel qual caso restituisce solo zero). Poiché wait_for_notify gestisce solo una singola notifica, dovrai chiamarla ripetutamente se desideri gestire più notifiche.

Quando si UNLISTEN channel_name o UNLISTEN *, Postgres si smettere di spingere le notifiche alla lista della tua connessione, ma quelli che sono già stati spinti a questo elenco rimarrà lì, e wait_for_notify sarà ancora restituirli quando è prossima chiamata. Ciò potrebbe causare un problema in cui le notifiche accumulate dopo lo wait_for_notify ma prima dello UNLISTEN restano attive e sono ancora presenti quando un altro thread controlla tale connessione.In tal caso, dopo UNLISTEN, è possibile chiamare wait_for_notify con brevi timeout fino a quando non restituisce nulla. Ma a meno che tu non stia facendo un uso intenso di LISTEN e NOTIFY per molti scopi diversi, tuttavia, probabilmente non vale la pena preoccuparsi.

Ho aggiunto un collegamento migliore all'implementazione di Sequel sopra, consiglierei di guardarlo. È piuttosto semplice.

+0

Solo per chiarimenti: se non lo fa ricevere una risposta in 1/2 secondo, smette di ascoltare? –

+0

Se non riceve una risposta entro il timeout specificato, wait_for_notify restituirà nil e il blocco non verrà chiamato, ma LISTEN per channel1 e channel2 rimarrà in vigore finché non si esclude. Quindi il tuo codice può andare a fare qualsiasi altra cosa, e quando attendi ancora per tornare a conoscere ancora continuerai ad ascoltare channel1 e channel2. Credo che se un NOTIFY si è verificato mentre non stavi bloccando su wait_for_notify, lo raccoglierai immediatamente quando attendi ancora una volta, ma non ne sono sicuro. – PreciousBodilyFluids

+0

Quindi avremmo bisogno di eseguire il polling di "wait_for_notify" per ottenere effettivamente il messaggio? –