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

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

9 ответов

Решение

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

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

Это звучит как сценарий, где zeroMQ будет хорошо подходить. Это среда обмена сообщениями, которая похожа на использование сокетов 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)

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

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

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

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

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

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

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

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

Обновление 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

ОБНОВЛЕНИЕ 2023 ГОДА

Я создаю библиотеку, которая позволяет использовать Python с nodejs.

Библиотека Javascript: https://github.com/7HR4IZ3/js_bridge .

Библиотека Python: https://github.com/7HR4IZ3/py_bridge .

Пример из JavaScript

      const { python } = require("js_bridge");
const py = python();

async function myPythonFunction() {
   let math = await py.import("math");
    let result = await math.sqrt(16);
    console.log(result);
}

myPythonFunction();

Пример из Python

      from py_bridge import nodejs

node1 = nodejs(port=7000)
node2 = nodejs(port-7001)

node1.setup(name="my_node")
node2.setup(name="nodejs2") # name parameter is for imports

node1.console.log("Hello from node1")
node2.console.log("Hello from node2")

fs = node1.require("fs") # also supports 'from my_node import fs'
print(fs.readSync("./mytext.txt"))

Я бы порекомендовал вам pymport.

Это обеспечивает полную совместимость всего кода Python с Node.js, включая двоичные модули:

      npm install pymport
npx pympip3 install numpy

затем просто в JavaScript:

      const { pymport, proxify } = require('pymport');
const np = proxify(pymport('numpy'));

const a = np.array([2, 3, 4]);
console.log(a.toString());

(я его автор)

Другие вопросы по тегам