Объединение node.js и Python

Node.js идеально подходит для нашего веб-проекта, но есть несколько вычислительных задач, для которых мы предпочли бы Python. У нас также есть код Python для них. Мы очень обеспокоены скоростью, что является самым элегантным способом вызова "рабочего" Python из node.js в асинхронном неблокирующем режиме?

Ответ 1

Для связи между node.js и сервером Python я бы использовал сокеты Unix, если оба процесса выполнялись на том же сервере и сокетах TCP/IP в противном случае. Для протокола маршалинга я бы взял JSON или буфер протокола. Если поточный Python окажется узким местом, подумайте об использовании Twisted Python, который обеспечивает то же событие, что и concurrency, как и node.js.

Если вы чувствуете себя авантюрно, узнайте clojure (clojurescript, clojure-py), и вы получите тот же язык, который запускается и взаимодействует с существующим кодом на Java, JavaScript (node.js), CLR и Python. И вы получаете превосходный протокол маршаллинга, просто используя структуры данных clojure.

Ответ 2

Это звучит как сценарий, в котором нульMQ будет подходящим. Это структура обмена сообщениями, похожая на использование сокетов TCP или Unix, но она гораздо более надежная (http://zguide.zeromq.org/py:all)

Там есть библиотека, которая использует zeroMQ для создания инфраструктуры RPC, которая работает очень хорошо. Он назывался zeroRPC (http://www.zerorpc.io/). Вот привет мир.

Сервер Python "Hello 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()

И клиент 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);
});

Или наоборот, 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");

И клиент 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)

Ответ 3

Если вы планируете, чтобы ваш рабочий Python выполнялся в отдельном процессе (как долговременный процесс типа сервера, так и порожденный ребенок по запросу), ваше сообщение с ним будет асинхронным со стороны node.js. Сокеты UNIX/TCP и связь stdin/out/err по сути async в node.

Ответ 4

Я бы также рассмотрел Apache Thrift http://thrift.apache.org/

Он может соединяться между несколькими языками программирования, является высокоэффективным и поддерживает асинхронные или синхронные вызовы. См. Здесь полные функции http://thrift.apache.org/docs/features/

Многоязычный язык может быть полезен для будущих планов, например, если вы позже захотите сделать часть вычислительной задачи на С++, очень легко добавить его в микс с помощью Thrift.

Ответ 5

У меня был большой успех, используя thoonk.js вместе с thoonk.py. Thoonk использует Redis (хранилище ключей для хранения в памяти), чтобы предоставить вам канал (думаю, публикуйте/подписаться), шаблоны очереди и работы для связи.

Почему это лучше, чем unix-сокеты или прямые сокеты tcp? Общая производительность может быть немного уменьшена, однако Thoonk предоставляет действительно простой API, который упрощает ручную работу с сокетом. Thoonk также помогает сделать тривиальную реализацию распределенной вычислительной модели, которая позволяет масштабировать работу ваших питонов для повышения производительности, поскольку вы просто закручиваете новые экземпляры ваших сотрудников python и подключаете их к тому же серверу redis.

Ответ 6

Я бы рекомендовал использовать некоторую рабочую очередь, используя, например, отличный Gearman, который предоставит вам отличный способ отправлять фоновые задания и асинхронно получать их результат после их обработки.

Преимущество этого, широко используемое в Digg (среди многих других), заключается в том, что он обеспечивает сильный, масштабируемый и надежный способ заставить рабочих на любом языке говорить с клиентами на любом языке.

Ответ 7

Обновление 2019

Есть несколько способов достичь этого, и вот список в возрастающем порядке сложности

  1. Python Shell, вы будете записывать потоки в консоль python и она будет писать вам обратно
  2. Redis Pub Sub, вы можете прослушивать канал на Python, пока ваш издатель js отправляет данные
  3. Соединение Websocket, где Node выступает в роли клиента, а Python выступает в качестве сервера или наоборот.
  4. Соединение API с Express/Flask/Tornado и т.д., Работающее отдельно с конечной точкой API, доступной для другого запроса

Подход 1 Python Shell Самый простой подход

файл 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);
})

файл 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()

Примечания: Создайте папку с именем subscriber, которая находится на том же уровне, что и файл source.js, и поместите destination.py внутри нее. Не забудьте изменить свою среду virtualenv