web-dev-qa-db-fra.com

Le pubsub Redis non bloquant est-il possible?

Je veux utiliser pubsub de redis pour transmettre certains messages, mais je ne veux pas être bloqué avec listen, comme le code ci-dessous:

import redis
rc = redis.Redis()

ps = rc.pubsub()
ps.subscribe(['foo', 'bar'])

rc.publish('foo', 'hello world')

for item in ps.listen():
    if item['type'] == 'message':
        print item['channel']
        print item['data']

La dernière section for sera bloquée. Je veux juste vérifier si un canal donné a des données, comment puis-je accomplir cela? Existe-t-il une méthode semblable à check?

24
limboy

Je ne pense pas que ce serait possible. Un canal ne contient pas de "données actuelles", vous vous abonnez à un canal et commencez à recevoir des messages qui sont envoyés par d'autres clients du canal; il s'agit donc d'une API de blocage. De même, si vous examinez la documentation Redis Commands pour pub/sub, cela serait plus clair.

7
Ankur

Si vous envisagez un traitement asynchrone non bloquant, vous utilisez (ou devriez utiliser) un environnement/serveur asynchrone.

UPDATE: Cela fait 5 ans que la réponse originale a été donnée, Python a eu une asynchrone native IO support . Il y a maintenant AIORedis, un client asynchrone IO Redis

43
vartec

La nouvelle version de redis-py prend en charge les publications asynchrones, consultez https://github.com/andymccurdy/redis-py pour plus de détails. Voici un exemple tiré de la documentation:

while True:
    message = p.get_message()
    if message:
        # do something with the message
    time.sleep(0.001)  # be Nice to the system :)
11

La réponse acceptée est obsolète car redis-py vous recommande d'utiliser la get_message() non bloquante. Mais cela permet également d’utiliser facilement les threads. 

https://pypi.python.org/pypi/redis

Il existe trois stratégies différentes pour lire les messages.

Dans les coulisses, get_message () utilise le module ‘select’ du système pour interroger rapidement le socket de la connexion. Si des données sont disponibles pour la lecture, get_message () les lira, formatera le message et le renverra ou le transmettra à un gestionnaire de messages. S'il n'y a aucune donnée à lire, get_message () retournera immédiatement None. Cela rend facile l'intégration dans une boucle d'événement existante de votre application.

 while True:
     message = p.get_message()
     if message:
         # do something with the message
     time.sleep(0.001)  # be Nice to the system :)

Les anciennes versions de redis-py ne lisaient que les messages avec pubsub.listen (). listen () est un générateur qui bloque jusqu'à ce qu'un message soit disponible. Si votre application n’a pas besoin de faire autre chose que de recevoir et d’agir sur les messages reçus de redis, listen () est un moyen facile de se lancer.

 for message in p.listen():
     # do something with the message

La troisième option exécute une boucle d'événement dans un thread séparé. pubsub.run_in_thread () crée un nouveau thread et lance la boucle d'événement. L'objet thread est renvoyé à l'appelant de run_in_thread (). L'appelant peut utiliser la méthode thread.stop () pour arrêter la boucle d'événement et le thread. En coulisse, il s’agit simplement d’une enveloppe autour de get_message () qui s'exécute dans un thread distinct, créant essentiellement une petite boucle d’événements non bloquante pour vous. run_in_thread () prend un argument optionnel sleep_time. Si spécifié, la boucle d'événements appellera time.sleep () avec la valeur à chaque itération de la boucle.

Remarque: comme nous exécutons un thread séparé, il n’ya aucun moyen de gérer les messages qui ne sont pas automatiquement traités avec les gestionnaires de messages enregistrés. Par conséquent, redis-py vous empêche d'appeler run_in_thread () si vous êtes abonné à des modèles ou à des canaux pour lesquels aucun gestionnaire de messages n'est associé.

p.subscribe(**{'my-channel': my_handler})
thread = p.run_in_thread(sleep_time=0.001)
# the event loop is now running in the background processing messages
# when it's time to shut it down...
thread.stop()

Donc, pour répondre à votre question, consultez simplement get_message si vous souhaitez savoir si un message est arrivé.

10
dalore

Ceci est un exemple de travail pour enfiler l'écouteur bloquant.

import sys
import cmd
import redis
import threading


def monitor():
    r = redis.Redis(YOURHOST, YOURPORT, YOURPASSWORD, db=0)

    channel = sys.argv[1]
    p = r.pubsub()
    p.subscribe(channel)

    print 'monitoring channel', channel
    for m in p.listen():
        print m['data']


class my_cmd(cmd.Cmd):
    """Simple command processor example."""

    def do_start(self, line):
        my_thread.start()

    def do_EOF(self, line):
        return True


if __== '__main__':
    if len(sys.argv) == 1:
        print "missing argument! please provide the channel name."
    else:
        my_thread = threading.Thread(target=monitor)
        my_thread.setDaemon(True)

        my_cmd().cmdloop()
6
dirkk0

Voici une solution non bloquante sans thread:

fd = ps.connection._sock.fileno();
rlist,, = select.select([fd], [], [], 0) # or replace 0 with None to block
if rlist:
    for rfd in rlist:
        if fd == rfd:
            message = ps.get_message()

ps.get_message() est suffisant en soi, mais j'utilise cette méthode pour pouvoir attendre plusieurs fds au lieu de la connexion redis.

2
chacham15

L'approche la plus efficace serait basée sur les greenlets plutôt que sur les threads. En tant que framework de concurrence basé sur les greenlets, gevent est déjà bien établi dans le monde Python. Une intégration de gevent avec redis-py serait donc merveilleuse. C’est exactement ce dont il est question dans ce numéro sur github:

https://github.com/andymccurdy/redis-py/issues/310

1
Jan-Philip Gehrcke

Pour atteindre un code sans blocage, vous devez utiliser un autre type de code de paradigme. Ce n'est pas difficile, utiliser un nouveau fil pour écouter tous les changements et laisser le fil principal faire autre chose. 

En outre, vous aurez besoin d'un mécanisme pour échanger des données entre le thread principal et le thread d'abonné Redis.

1
pfreixes

Redis 'pub/sub envoie des messages aux clients abonnés (écoute) sur un canal. Si vous n'écoutez pas, vous manquerez le message (d'où l'appel bloquant). Si vous voulez que cela ne soit pas bloquant, je vous recommande d'utiliser une file d'attente (redis est assez bon pour ça aussi). Si vous devez utiliser pub/sub, vous pouvez utiliser, comme suggéré, un écouteur bloquant asynchrone pour envoyer des messages dans une file d'attente et utiliser un consommateur distinct pour traiter les messages de cette file d'attente de manière non bloquante.

0
Glaslos

Vous pouvez utiliser gevent, gevent singe patcher pour créer une application redis pubsub non bloquante.

0
Sri