Combinando node.js y Python

127

Node.js es una combinación perfecta para nuestro proyecto web, pero hay pocas tareas computacionales para las cuales preferiríamos Python. También ya tenemos un código Python para ellos. Estamos muy preocupados por la velocidad, ¿cuál es la forma más elegante de llamar a un "trabajador" de Python desde node.js de forma asincrónica sin bloqueo?

Cartesius00
fuente
3
Hola, ¿podría compartir con nosotros qué eligió y cómo funcionó para usted? Hay bibliotecas en Python que todos amamos usar mientras mantenemos el rendimiento y las opciones sin bloqueo. Gracias
Maziyar
¿Qué pasa con simplemente generar / bifurcar un proceso y comunicarse a través del sistema IO, como este sugiere: sohamkamani.com/blog/2015/08/21/python-nodejs-comm ?
lkahtz
Hay una nueva biblioteca puente llamada PyNode que le permite llamar a Python y obtener los tipos JS devueltos. Aquí se demuestra thecodinginterface.com/blog/…
SciGuyMcQ

Respuestas:

86

Para la comunicación entre node.js y el servidor Python, usaría sockets Unix si ambos procesos se ejecutan en el mismo servidor y los sockets TCP / IP de lo contrario. Para el protocolo de clasificación, tomaría JSON o buffer de protocolo . Si Python roscado parece ser un cuello de botella, considere usar Twisted Python , que proporciona la misma concurrencia impulsada por eventos que node.js.

Si se siente aventurero, aprenda clojure ( clojurescript , clojure-py ) y obtendrá el mismo lenguaje que se ejecuta e interopera con el código existente en Java, JavaScript (incluido node.js), CLR y Python. Y obtienes un excelente protocolo de clasificación simplemente usando estructuras de datos de clojure.

Aleš Kotnik
fuente
2
¿Sabes si algo como esto funcionará en Heroku, que tiene un sistema de archivos efímero?
cm2
119

Esto suena como un escenario donde zeroMQ encajaría bien. Es un marco de mensajería similar al uso de sockets TCP o Unix, pero es mucho más robusto ( http://zguide.zeromq.org/py:all )

Hay una biblioteca que usa zeroMQ para proporcionar un marco RPC que funciona bastante bien. Se llama zeroRPC ( http://www.zerorpc.io/ ). Aquí está el hola mundo.

Servidor Python "Hola x":

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 __name__ == "__main__" : main()

Y el cliente 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);
});

O viceversa, el servidor 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");

Y el cliente 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)
djheru
fuente
44
¿Puede zerorpc manejar múltiples estados en caso de que haya múltiples sesiones de cliente?
user1027169
Buena respuesta, ejemplos de muestra, abundante explicación y lo que estaba buscando. TY. +1
Gaurav Gandhi
1
si eres nuevo como yo, instala las dependencias que mencionaron aquí - ianhinsdale.com/code/2013/12/08/…
Darpan
¡Muchas gracias por esto!
Gezim
1
¡Bonita demo de Hello World! Otra solución similar a continuación con Rabbitmq. medium.com/@HolmesLaurence/…
teng
7

Si hace arreglos para que su trabajador de Python esté en un proceso separado (ya sea un proceso de tipo servidor de larga ejecución o un hijo generado bajo demanda), su comunicación con él será asíncrona en el lado de node.js. Los sockets UNIX / TCP y la comunicación stdin / out / err son inherentemente asíncronos en el nodo.

lanzz
fuente
6

Consideraría también Apache Thrift http://thrift.apache.org/

Se puede conectar entre varios lenguajes de programación, es altamente eficiente y tiene soporte para llamadas asíncronas o sincronizadas. Ver características completas aquí http://thrift.apache.org/docs/features/

El lenguaje múltiple puede ser útil para planes futuros, por ejemplo, si luego desea realizar parte de la tarea computacional en C ++, es muy fácil agregarlo a la mezcla usando Thrift.

Iftah
fuente
5

He tenido mucho éxito usando thoonk.js junto con thoonk.py . Thoonk aprovecha Redis (almacén de valores clave en memoria) para proporcionarle alimentación (piense en publicar / suscribirse), cola y patrones de trabajo para la comunicación.

¿Por qué es esto mejor que los sockets unix o los sockets tcp directos? El rendimiento general puede disminuir un poco, sin embargo, Thoonk proporciona una API realmente simple que simplifica tener que lidiar manualmente con un socket. Thoonk también ayuda a que sea realmente trivial implementar un modelo de computación distribuida que le permita escalar sus trabajadores de python para aumentar el rendimiento, ya que simplemente activa nuevas instancias de sus trabajadores de python y las conecta al mismo servidor redis.

Doug McCall
fuente
3

Recomiendo usar algunas colas de trabajo utilizando, por ejemplo, el excelente Gearman , que le proporcionará una excelente manera de enviar trabajos en segundo plano y obtener su resultado de forma asincrónica una vez que se procesen.

La ventaja de esto, utilizada en gran medida en Digg (entre muchos otros) es que proporciona una forma sólida, escalable y robusta de hacer que los trabajadores en cualquier idioma hablen con los clientes en cualquier idioma.

Pierre
fuente
1

Actualización 2019

Hay varias formas de lograr esto y aquí está la lista en orden creciente de complejidad

  1. Python Shell, escribirá secuencias en la consola de Python y le responderá
  2. Redis Pub Sub, puede tener un canal escuchando en Python mientras su editor de nodo js empuja datos
  3. Conexión Websocket donde Node actúa como el cliente y Python actúa como el servidor o viceversa
  4. La conexión API con Express / Flask / Tornado, etc., funciona por separado con un punto final API expuesto para que el otro lo consulte

Enfoque 1 Python Shell Enfoque más simple

archivo 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);
})

archivo 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()

Notas : Cree una carpeta llamada suscriptor que esté al mismo nivel que el archivo source.js y coloque destination.py dentro de ella. No olvides cambiar tu entorno virtualenv

PirateApp
fuente