javascript

Как управлять несколькими потоками в Node JS

  • вторник, 22 июня 2021 г. в 00:36:17
https://habr.com/ru/company/otus/blog/563920/
  • Блог компании OTUS
  • JavaScript
  • Программирование
  • Node.JS


В этом посте я собираюсь показать вам, как потенциально утроить производительность вашего приложения Node за счет управления несколькими потоками. Это важный учебник, в котором показанные методы и примеры дадут вам все необходимое для настройки эффективного управления потоками.

В папке worker-pool у нас есть 2 файла: один - это логика контроллера для Worker Pool (controller.js). Другой (файл) содержит функции, которые будут запускаться потоками... он же так называемый промежуточный слой, о котором я говорил ранее (thread-functions.js).

worker-pool/controller.js

'use strict'

const WorkerPool = require('workerpool')
const Path = require('path')

let poolProxy = null

// FUNCTIONS
const init = async (options) => {
  const pool = WorkerPool.pool(Path.join(__dirname, './thread-functions.js'), options)
  poolProxy = await pool.proxy()
  console.log(`Worker Threads Enabled - Min Workers: ${pool.minWorkers} - Max Workers: ${pool.maxWorkers} - Worker Type: ${pool.workerType}`)
}

const get = () => {
  return poolProxy
}

// EXPORTS
exports.init = init
exports.get = get

В файле controller.js мы используем модуль workerpool. У нас также есть 2 экспортируемые функции, которые называются init и get. Функция init будет выполняться один раз во время загрузки нашего приложения. Она инстанцирует Worker Pool с опциями, которые мы предоставим, и ссылкой на thread-functions.js. Она также создает прокси, который будет храниться в памяти до тех пор, пока работает наше приложение. Функция get просто возвращает прокси в памяти.

worker-pool/thread-functions.js

'use strict'

const WorkerPool = require('workerpool')
const Utilities = require('../2-utilities')

// MIDDLEWARE FUNCTIONS
const bcryptHash = (password) => {
  return Utilities.bcryptHash(password)
}

// CREATE WORKERS
WorkerPool.worker({
  bcryptHash
})

В файле thread-functions.js создадим воркер-функции, которые будут управляться Worker Pool. В нашем примере применим BcryptJS для хэширования паролей. Это обычно занимает около 10 миллисекунд, в зависимости от скорости работы используемой машины, и является хорошим решением для трудоемких задач. Внутри файла utilities.js находится функция и логика, которая хэширует пароль. Все, что мы делаем в функциях потока, заключается в выполнении этого bcryptHash через функцию workerpool. Таким образом, мы сохраняем код централизованным и избегаем дублирования или путаницы в том, где существуют определенные операции.

2-utilities.js

'use strict'

const BCrypt = require('bcryptjs')

const bcryptHash = async (password) => {
  return await BCrypt.hash(password, 8)
}

exports.bcryptHash = bcryptHash

.env

NODE_ENV="production"
PORT=6000
WORKER_POOL_ENABLED="1"

Файл .env содержит номер порта и устанавливает переменную NODE_ENV на "production". Здесь же мы указываем, хотим ли мы включить или отключить Worker Pool, устанавливая WORKER_POOL_ENABLED в "1" или "0".

1-app.js

'use strict'

require('dotenv').config()

const Express = require('express')
const App = Express()
const HTTP = require('http')
const Utilities = require('./2-utilities')
const WorkerCon = require('./worker-pool/controller')

// Router Setup
App.get('/bcrypt', async (req, res) => {
  const password = 'This is a long password'
  let result = null
  let workerPool = null

  if (process.env.WORKER_POOL_ENABLED === '1') {
    workerPool = WorkerCon.get()
    result = await workerPool.bcryptHash(password)
  } else {
    result = await Utilities.bcryptHash(password)
  }

  res.send(result)
})

// Server Setup
const port = process.env.PORT
const server = HTTP.createServer(App)

;(async () => {
  // Init Worker Pool
  if (process.env.WORKER_POOL_ENABLED === '1') {
    const options = { minWorkers: 'max' }
    await WorkerCon.init(options)
  }

  // Start Server
  server.listen(port, () => {
    console.log('NodeJS Performance Optimizations listening on: ', port)
  })
})()

И последнее, файл 1-app.js содержит код, который будет выполняться при запуске нашего приложения. Сначала инициализируем переменные в файле .env. Затем настроим сервер Express и создадим маршрут под названием /bcrypt. При запуске этого маршрута проверим, включен ли Worker Pool. Если да, то получим управление прокси Worker Pool и выполним функцию bcryptHash, которую мы объявили в файле thread-functions.js. Она, в свою очередь, выполнит функцию bcryptHash в Utilities и вернет нам результат. Если Worker Pool отключен, тогда просто выполним функцию bcryptHash непосредственно в Utilities.

В конце нашего файла 1-app.js находится функция, вызывающая саму себя. Это сделано для поддержки async/await, которую мы используем при взаимодействии с Worker Pool. Далее инициализируем Worker Pool, если он включен. Единственная конфигурация, которую надо переопределить - установка minWorkers на "max". Это гарантирует, что Worker Pool породит столько потоков, сколько логических ядер есть на нашей машине, за исключением 1 логического ядра, которое используется для главного потока. В моем случае имеется 6 физических ядер с гиперпоточностью, это означает, что у меня в распоряжении 12 логических ядер. Поэтому при значении minWorkers равном "max", Worker Pool будет создавать и управлять 11 потоками. Наконец, последний фрагмент кода - это запуск нашего сервера и прослушивание порта 6000.

Тестирование Worker Pool

Тестировать Worker Pool очень просто: запустите приложение и во время его работы выполните запрос get на http://localhost:6000/bcrypt. Если у вас есть инструментарий нагрузочного тестирования, такой как AutoCannon, вы можете посмотреть разницу в производительности при включении/выключении Worker Pool. AutoCannon очень прост в использовании.

Заключение

Я надеюсь, что это руководство дало вам представление об управлении несколькими потоками в вашем приложении Node. Вложенное видео в начале статьи наглядно демонстрирует процесс тестирования приложения Node.


Перевод подготовлен в рамках курса "Node.js Developer". Если интересно узнать о курсе больше, регистрируйтесь на день открытых дверей.