Połączenie node.js i Pythona

127

Node.js doskonale pasuje do naszego projektu internetowego, ale jest kilka zadań obliczeniowych, dla których wolelibyśmy Python. Mamy też już dla nich kod w Pythonie. Jesteśmy bardzo zaniepokojeni szybkością. Jaki jest najbardziej elegancki sposób wywoływania „pracownika” Pythona z node.js w sposób asynchroniczny i nieblokujący?

Cartesius00
źródło
3
Cześć, czy możesz nam powiedzieć, co wybrałeś i jak to u Ciebie wyszło? W Pythonie są biblioteki, z których wszyscy uwielbiamy korzystać, zachowując jednocześnie wydajność i opcje nieblokujące. Dzięki
Maziyar
A co z po prostu spawnowaniem / rozwidleniem procesu i komunikacją przez system IO, jak sugeruje ten: sohamkamani.com/blog/2015/08/21/python-nodejs-comm ?
lkahtz
Istnieje nowa biblioteka mostkująca o nazwie PyNode, która umożliwia wywoływanie Pythona i zwracanie typów JS. Zostało to pokazane tutaj thecodinginterface.com/blog/…
SciGuyMcQ

Odpowiedzi:

86

Do komunikacji między node.js a serwerem Python użyłbym gniazd Unix, jeśli oba procesy działają na tym samym serwerze, a gniazda TCP / IP w przeciwnym razie. Dla protokołu kierowania wziąłbym JSON lub bufor protokołu . Jeśli wątkowym Python okaże się wąskim gardłem, rozważ użycie Twisted Python , który zapewnia tę samą współbieżność sterowaną zdarzeniami, co node.js.

Jeśli czujesz się na siłach , naucz się clojure ( clojurescript , clojure-py ), a uzyskasz ten sam język, który działa i współpracuje z istniejącym kodem w Javie, JavaScript (w tym node.js), CLR i Pythonie. I otrzymujesz doskonały protokół krosowania, po prostu używając struktur danych clojure.

Aleš Kotnik
źródło
2
Czy wiesz, czy coś takiego zadziała na Heroku, który ma efemeryczny system plików?
cm2,
119

Brzmi to jak scenariusz, w którym zeroMQ byłoby dobrym rozwiązaniem. Jest to platforma przesyłania wiadomości podobna do korzystania z gniazd TCP lub Unix, ale jest znacznie bardziej niezawodna ( http://zguide.zeromq.org/py:all )

Istnieje biblioteka, która wykorzystuje zeroMQ, aby zapewnić platformę RPC, która działa całkiem dobrze. Nazywa się zeroRPC ( http://www.zerorpc.io/ ). Oto witaj świecie.

Serwer „Hello x” w Pythonie:

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

I klient 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);
});

Lub odwrotnie, serwer 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");

I klient Pythona

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
źródło
4
Czy Zerorpc może obsługiwać wiele stanów w przypadku wielu sesji klienta?
user1027169
Dobra odpowiedź, przykładowe przykłady, liczne wyjaśnienia i czego szukałem. TY. +1
Gaurav Gandhi
1
jeśli jesteś nowy jak ja, zainstaluj zależności, o których tutaj wspomnieli - ianhinsdale.com/code/2013/12/08/…
Darpan
Wielkie dzięki za to!
Gezim
1
Niezłe demo Hello World! Kolejne podobne rozwiązanie poniżej przy użyciu Rabbitmq. medium.com/@HolmesLaurence/…
teng
7

Jeśli zaplanujesz, że twój Python Worker będzie miał oddzielny proces (albo długo działający proces typu serwerowego, albo zrodzone dziecko na żądanie), twoja komunikacja z nim będzie asynchroniczna po stronie node.js. Gniazda UNIX / TCP i komunikacja stdin / out / err są z natury asynchroniczne w węźle.

lanzz
źródło
6

Rozważyłbym również Apache Thrift http://thrift.apache.org/

Może łączyć kilka języków programowania, jest bardzo wydajny i obsługuje wywołania asynchroniczne lub synchronizacyjne. Zobacz pełne funkcje tutaj http://thrift.apache.org/docs/features/

Wielojęzyczny język może być przydatny w przyszłych planach, na przykład jeśli później chcesz wykonać część zadania obliczeniowego w C ++, bardzo łatwo jest dodać go do mieszanki za pomocą Thrift.

Iftah
źródło
5

Odniosłem duży sukces, używając thoonk.js wraz z thoonk.py . Thoonk wykorzystuje Redis (magazyn wartości klucza w pamięci), aby zapewnić kanał (pomyśl o publikowaniu / subskrybowaniu), kolejki i wzorce zadań do komunikacji.

Dlaczego jest to lepsze niż gniazda unix lub bezpośrednie gniazda TCP? Ogólna wydajność może zostać nieco zmniejszona, jednak Thoonk zapewnia naprawdę proste API, które upraszcza konieczność ręcznej obsługi gniazda. Thoonk pomaga również sprawić, że wdrożenie rozproszonego modelu obliczeniowego, który pozwala skalować pracowników Pythona w celu zwiększenia wydajności, jest naprawdę trywialne, ponieważ po prostu uruchamiasz nowe instancje pracowników Python i podłączasz je do tego samego serwera redis.

Doug McCall
źródło
3

Poleciłbym korzystanie z kolejki roboczej , na przykład przy użyciu doskonałego Gearmana , który zapewni Ci świetny sposób na wysyłanie zadań w tle i asynchroniczne uzyskiwanie ich wyników po ich przetworzeniu.

Zaletą tego, często używanego w Digg (między innymi), jest to, że zapewnia silny, skalowalny i niezawodny sposób, aby pracownicy mogli rozmawiać z klientami w dowolnym języku w dowolnym języku.

Pierre
źródło
1

Zaktualizuj 2019

Istnieje kilka sposobów osiągnięcia tego celu, a oto lista w kolejności rosnącej złożoności

  1. Python Shell, napiszesz strumienie do konsoli Pythona, a ona odpisze ci
  2. Redis Pub Sub, możesz mieć kanał nasłuchujący w Pythonie, podczas gdy wydawca node js wypycha dane
  3. Połączenie Websocket, w którym Node działa jako klient, a Python jako serwer lub odwrotnie
  4. Połączenie API z Express / Flask / Tornado itp.pracuje oddzielnie z punktem końcowym API udostępnionym innym do wykonywania zapytań

Podejście 1 Python Shell Najprostsze podejście

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

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

Uwagi : Stwórz folder o nazwie subscriber, który jest na tym samym poziomie co plik source.js i umieść w nim miejsce docelowe.py. Nie zapomnij zmienić swojego środowiska virtualenv

PirateApp
źródło