Как можно выполнить код только на одном из запущенных инстансов сервиса на Node.js?

Я разрабатываю сервис на Node.js, который запускается в нескольких инстансах. У меня есть функция, выполняющаяся раз в N минут в фоновом режиме (делает запрос к стороннему API и сохраняет результат). 

Проблема заключается в том, что из-за работы нескольких инстансов запрос к стороннему API осуществляется несколько раз. Как можно решить эту проблему с учетом следующих условий:

- Необходимо обеспечить выполнение скрипта даже в случае, если любой из инстансов может остановиться (нельзя полагаться на один "мастер-инстанс", так как он тоже может выйти из строя).
- Нужно гарантировать, что функция будет выполнена только на одном инстансе.
  • 25 декабря 2024 г. 17:23
Ответы на вопрос 3
Для решения проблемы выполнения кода только на одном из нескольких инстансов вашего Node.js сервиса вы можете использовать различные подходы. Самыми распространенными являются использование базы данных, распределенных блокировок и систем управления заданиями. Вот несколько рекомендуемых подходов:

### 1. Использование БД для распределенной блокировки

Можно использовать базу данных, чтобы гарантировать, что только один инстанс выполнит задачу в конкретное время. Например, вы можете создать табличку блокировок и по мере выполнения операции вставлять в нее запись.

Пример с использованием MongoDB:

```javascript
const mongoose = require('mongoose');
const { Schema } = mongoose;

const LockSchema = new Schema({
    job: { type: String, unique: true },
    locked: { type: Boolean, default: true },
    expiresAt: { type: Date },
});

const Lock = mongoose.model('Lock', LockSchema);

async function acquireLock() {
    const now = new Date();
    const expiresAt = new Date(now.getTime() + 5 * 60 * 1000); // блокировка на 5 минут

    const lock = await Lock.findOneAndUpdate(
        { job: 'myUniqueJob', locked: false, expiresAt: { $lt: now } },
        { locked: true, expiresAt },
        { new: true, upsert: true }
    );

    return lock && lock.locked;
}

async function releaseLock() {
    await Lock.updateOne({ job: 'myUniqueJob' }, { locked: false });
}

async function runJob() {
    if (await acquireLock()) {
        try {
            // здесь выполняем задачу
            console.log('Запускаем задачу');
        } finally {
            await releaseLock();
        }
    } else {
        console.log('Задача уже выполняется другим инстансом');
    }
}

// Запуск задачи (например, каждые N минут)
setInterval(runJob, N * 60 * 1000);
```

### 2. Использование Redis

Redis предоставляет механизм распределенных блокировок (например, с помощью библиотеки `node-redis-lock`, `redlock`). Это может быть более простым и производительным решением, чем работа с БД.

Пример использования `redlock`:

```javascript
const Redis = require('ioredis');
const Redlock = require('redlock');

const redis = new Redis();
const redlock = new Redlock([redis]);

async function runJob() {
    try {
        const lock = await redlock.lock('locks:myUniqueJob', 5000); // блокировка на 5 секунд
        // здесь выполняем задачу
        console.log('Запускаем задачу');
        // Освобождаем блокировку
        await lock.unlock();
    } catch (err) {
        console.log('Не удалось запустить задачу: ' + err.message);
    }
}

// Запуск задачи (например, каждые N минут)
setInterval(runJob, N * 60 * 1000);
```

### 3. Использование систем управления заданиями

Так же вы можете использовать системы планирования и управления заданиями, такие как **Bull**, **Agenda**, **Kue** и др. Они могут справиться с планированием и распределением работ по вашим инстансам.

### 4. Использование message queue (например, RabbitMQ, Kafka)

Если у вас есть инфраструктура с очередями сообщений, вы можете поставить задачу в очередь и позволить только одному инстансу забирать её. Это также гарантирует устойчивость к сбоям.

Каждый из этих методов имеет свои плюсы и минусы, и выбор зависит от вашей архитектуры и требований. Для большинства приложений, где необходимо просто гарантировать выполнение кода на одном инстансе, использование Redis или БД с распределенными блокировками будут наиболее подходящими вариантами.
ну например, общая очередь задач, но вопрос всё таки очень общий
Зависит от того, на чем бежит сервис. 
В Кубернетисе есть "распределенный cronjob", то же самое в AWS ECS насколько помню.
Если запускать без контейнеров, то БД или очередь можно использовать для этого.
Похожие вопросы