Node.js convient parfaitement à notre projet Web, mais il existe peu de tâches de calcul pour lesquelles nous préférerions Python. Nous avons également déjà un code Python pour eux. Nous sommes très préoccupés par la vitesse. Quelle est la façon la plus élégante d’appeler un Python "worker" à partir de node.js de manière asynchrone et non bloquante?
Pour la communication entre node.js et le serveur Python, j’utiliserais les sockets Unix si les deux processus s’exécutent sur le même serveur et les mêmes sockets TCP/IP. Pour le protocole de marshaling, je prendrais JSON ou tampon de protocole . Si threaded Python se révèle être un goulot d'étranglement, envisagez d'utiliser Twisted Python , qui fournit le même accès concurrentiel piloté par les événements que node.js.
Si vous vous sentez aventureux, apprenez clojure ( clojurescript , clojure-py ) et vous obtiendrez le même langage qui s'exécute et interagit avec le code existant. sur Java, JavaScript (node.js inclus), CLR et Python. Et vous obtenez un superbe protocole de triage en utilisant simplement des structures de données clojure.
Cela ressemble à un scénario où zeroMQ conviendrait bien. C'est un framework de messagerie qui s'apparente à l'utilisation de TCP ou de sockets Unix, mais il est beaucoup plus robuste ( http://zguide.zeromq.org/py:all )
Il existe une bibliothèque qui utilise zeroMQ pour fournir une structure RPC qui fonctionne plutôt bien. Il s'appelle zeroRPC ( http://www.zerorpc.io/ ). Voici le monde bonjour.
Python "Bonjour x" serveur:
import zerorpc
class HelloRPC(object):
'''pass the method a name, it replies "Hello name!"'''
def hello(self, name):
return "Hello, {0}!".format(name)
def main():
s = zerorpc.Server(HelloRPC())
s.bind("tcp://*:4242")
s.run()
if __== "__main__" : main()
Et le client node.js:
var zerorpc = require("zerorpc");
var client = new zerorpc.Client();
client.connect("tcp://127.0.0.1:4242");
//calls the method on the python object
client.invoke("hello", "World", function(error, reply, streaming) {
if(error){
console.log("ERROR: ", error);
}
console.log(reply);
});
Ou inversement, le serveur node.js:
var zerorpc = require("zerorpc");
var server = new zerorpc.Server({
hello: function(name, reply) {
reply(null, "Hello, " + name, false);
}
});
server.bind("tcp://0.0.0.0:4242");
Et le client python
import zerorpc, sys
c = zerorpc.Client()
c.connect("tcp://127.0.0.1:4242")
name = sys.argv[1] if len(sys.argv) > 1 else "dude"
print c.hello(name)
Si vous organisez votre travailleur Python dans un processus distinct (processus de type serveur de longue durée ou enfant engendré à la demande), votre communication avec ce serveur sera asynchrone du côté de node.js. Les sockets UNIX/TCP et la communication stdin/out/err sont intrinsèquement asynchrones dans le noeud.
Je considérerais aussi Apache Thrift http://thrift.Apache.org/
Il peut faire la liaison entre plusieurs langages de programmation, est très efficace et prend en charge les appels asynchrones ou synchronisés. Voir les caractéristiques complètes ici http://thrift.Apache.org/docs/features/
Le multilingue peut être utile pour les projets futurs. Par exemple, si vous souhaitez effectuer ultérieurement une partie de la tâche de calcul en C++, il est très facile de l’ajouter au mélange à l’aide de Thrift.
J'ai eu beaucoup de succès en utilisant thoonk.js avec thoonk.py . Thoonk exploite Redis (magasin de valeurs-clés en mémoire) pour vous donner des modèles de flux (pensez publier/abonner), de files d'attente et de travaux pour la communication.
Pourquoi est-ce mieux que les sockets unix ou les sockets directs tcp? Les performances globales peuvent être un peu réduites, cependant Thoonk fournit une API très simple qui simplifie le traitement manuel d’un socket. Thoonk facilite également la mise en œuvre d'un modèle informatique distribué qui vous permet de redimensionner vos travailleurs python pour augmenter les performances, car vous venez de créer de nouvelles instances de vos python et de vous connecter. eux sur le même serveur redis.
Je vous recommande d'utiliser une file d'attente de travail en utilisant, par exemple, l'excellent Gearman , qui vous fournira un excellent moyen de répartir les tâches en arrière-plan et d'obtenir leur résultat de manière asynchrone une fois qu'elles sont traitées.
L’avantage de cette utilisation, largement utilisée chez Digg (entre autres), c’est qu’elle fournit un moyen puissant, évolutif et robuste de faire en sorte que les travailleurs de toutes les langues parlent aux clients dans toutes les langues.
Mise à jour 2019
Il y a plusieurs façons d'y parvenir et voici la liste par ordre croissant de complexité.
Approche 1 Python Approche Shell la plus simple
fichier source.js
const ps = require('python-Shell')
// very important to add -u option since our python script runs infinitely
var options = {
pythonPath: '/Users/zup/.local/share/virtualenvs/python_Shell_test-TJN5lQez/bin/python',
pythonOptions: ['-u'], // get print results in real-time
// make sure you use an absolute path for scriptPath
scriptPath: "./subscriber/",
// args: ['value1', 'value2', 'value3'],
mode: 'json'
};
const Shell = new ps.PythonShell("destination.py", options);
function generateArray() {
const list = []
for (let i = 0; i < 1000; i++) {
list.Push(Math.random() * 1000)
}
return list
}
setInterval(() => {
Shell.send(generateArray())
}, 1000);
Shell.on("message", message => {
console.log(message);
})
fichier destination.py
import datetime
import sys
import time
import numpy
import talib
import timeit
import json
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
size = 1000
p = 100
o = numpy.random.random(size)
h = numpy.random.random(size)
l = numpy.random.random(size)
c = numpy.random.random(size)
v = numpy.random.random(size)
def get_indicators(values):
# Return the RSI of the values sent from node.js
numpy_values = numpy.array(values, dtype=numpy.double)
return talib.func.RSI(numpy_values, 14)
for line in sys.stdin:
l = json.loads(line)
print(get_indicators(l))
# Without this step the output may not be immediately available in node
sys.stdout.flush()
Notes: Créez un dossier appelé abonné qui se trouve au même niveau que le fichier source.js et placez destination.py à l'intérieur. N'oubliez pas de changer votre environnement virtualenv